summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt14
-rw-r--r--dist/languages/uk.ts7321
-rw-r--r--externals/CMakeLists.txt11
m---------externals/dynarmic0
m---------externals/xbyak0
-rw-r--r--src/audio_core/CMakeLists.txt2
-rw-r--r--src/audio_core/in/audio_in_system.cpp2
-rw-r--r--src/audio_core/in/audio_in_system.h2
-rw-r--r--src/audio_core/out/audio_out_system.cpp4
-rw-r--r--src/audio_core/out/audio_out_system.h4
-rw-r--r--src/common/common_funcs.h4
-rw-r--r--src/common/concepts.h16
-rw-r--r--src/common/fs/file.h12
-rw-r--r--src/common/host_memory.cpp6
-rw-r--r--src/common/input.h5
-rw-r--r--src/common/settings.cpp1
-rw-r--r--src/core/CMakeLists.txt20
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp7
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp2
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.cpp22
-rw-r--r--src/core/arm/exclusive_monitor.cpp4
-rw-r--r--src/core/core.cpp9
-rw-r--r--src/core/file_sys/control_metadata.cpp43
-rw-r--r--src/core/file_sys/control_metadata.h6
-rw-r--r--src/core/hid/emulated_controller.cpp46
-rw-r--r--src/core/hid/emulated_controller.h5
-rw-r--r--src/core/hle/ipc_helpers.h19
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.h4
-rw-r--r--src/core/hle/kernel/global_scheduler_context.cpp22
-rw-r--r--src/core/hle/kernel/global_scheduler_context.h8
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp63
-rw-r--r--src/core/hle/kernel/hle_ipc.h37
-rw-r--r--src/core/hle/kernel/init/init_slab_setup.cpp75
-rw-r--r--src/core/hle/kernel/init/init_slab_setup.h2
-rw-r--r--src/core/hle/kernel/k_class_token.cpp3
-rw-r--r--src/core/hle/kernel/k_class_token.h6
-rw-r--r--src/core/hle/kernel/k_client_port.cpp7
-rw-r--r--src/core/hle/kernel/k_client_port.h3
-rw-r--r--src/core/hle/kernel/k_debug.h20
-rw-r--r--src/core/hle/kernel/k_dynamic_page_manager.h67
-rw-r--r--src/core/hle/kernel/k_dynamic_resource_manager.h3
-rw-r--r--src/core/hle/kernel/k_event.cpp2
-rw-r--r--src/core/hle/kernel/k_event_info.h64
-rw-r--r--src/core/hle/kernel/k_handle_table.cpp33
-rw-r--r--src/core/hle/kernel/k_handle_table.h106
-rw-r--r--src/core/hle/kernel/k_memory_block.h122
-rw-r--r--src/core/hle/kernel/k_memory_layout.cpp10
-rw-r--r--src/core/hle/kernel/k_memory_layout.h19
-rw-r--r--src/core/hle/kernel/k_memory_manager.cpp270
-rw-r--r--src/core/hle/kernel/k_memory_manager.h259
-rw-r--r--src/core/hle/kernel/k_memory_region_type.h123
-rw-r--r--src/core/hle/kernel/k_page_bitmap.h243
-rw-r--r--src/core/hle/kernel/k_page_buffer.h14
-rw-r--r--src/core/hle/kernel/k_page_group.h86
-rw-r--r--src/core/hle/kernel/k_page_heap.cpp86
-rw-r--r--src/core/hle/kernel/k_page_heap.h39
-rw-r--r--src/core/hle/kernel/k_page_table.cpp1129
-rw-r--r--src/core/hle/kernel/k_page_table.h142
-rw-r--r--src/core/hle/kernel/k_page_table_manager.h55
-rw-r--r--src/core/hle/kernel/k_page_table_slab_heap.h93
-rw-r--r--src/core/hle/kernel/k_port.cpp6
-rw-r--r--src/core/hle/kernel/k_process.cpp18
-rw-r--r--src/core/hle/kernel/k_resource_limit.cpp11
-rw-r--r--src/core/hle/kernel/k_resource_limit.h11
-rw-r--r--src/core/hle/kernel/k_scheduler.cpp35
-rw-r--r--src/core/hle/kernel/k_scheduler_lock.h3
-rw-r--r--src/core/hle/kernel/k_server_port.cpp6
-rw-r--r--src/core/hle/kernel/k_server_port.h19
-rw-r--r--src/core/hle/kernel/k_server_session.cpp187
-rw-r--r--src/core/hle/kernel/k_server_session.h43
-rw-r--r--src/core/hle/kernel/k_session.cpp7
-rw-r--r--src/core/hle/kernel/k_session.h3
-rw-r--r--src/core/hle/kernel/k_shared_memory.cpp6
-rw-r--r--src/core/hle/kernel/k_system_resource.cpp26
-rw-r--r--src/core/hle/kernel/k_system_resource.h137
-rw-r--r--src/core/hle/kernel/k_thread.cpp48
-rw-r--r--src/core/hle/kernel/k_thread.h10
-rw-r--r--src/core/hle/kernel/k_transfer_memory.cpp2
-rw-r--r--src/core/hle/kernel/kernel.cpp284
-rw-r--r--src/core/hle/kernel/kernel.h50
-rw-r--r--src/core/hle/kernel/physical_core.cpp4
-rw-r--r--src/core/hle/kernel/service_thread.cpp242
-rw-r--r--src/core/hle/kernel/service_thread.h6
-rw-r--r--src/core/hle/kernel/slab_helpers.h78
-rw-r--r--src/core/hle/kernel/svc.cpp126
-rw-r--r--src/core/hle/kernel/svc_results.h1
-rw-r--r--src/core/hle/kernel/svc_types.h508
-rw-r--r--src/core/hle/result.h13
-rw-r--r--src/core/hle/service/acc/acc.cpp34
-rw-r--r--src/core/hle/service/acc/acc.h1
-rw-r--r--src/core/hle/service/acc/acc_u0.cpp2
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp25
-rw-r--r--src/core/hle/service/acc/profile_manager.h3
-rw-r--r--src/core/hle/service/am/am.cpp13
-rw-r--r--src/core/hle/service/am/am.h1
-rw-r--r--src/core/hle/service/audio/audin_u.cpp2
-rw-r--r--src/core/hle/service/audio/audout_u.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp4
-rw-r--r--src/core/hle/service/kernel_helpers.cpp2
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.cpp8
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.h3
-rw-r--r--src/core/hle/service/nfp/nfp_device.cpp9
-rw-r--r--src/core/hle/service/nfp/nfp_device.h1
-rw-r--r--src/core/hle/service/nfp/nfp_types.h5
-rw-r--r--src/core/hle/service/nfp/nfp_user.cpp3
-rw-r--r--src/core/hle/service/nfp/nfp_user.h8
-rw-r--r--src/core/hle/service/nvdrv/core/nvmap.cpp5
-rw-r--r--src/core/hle/service/nvdrv/core/nvmap.h1
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp16
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_producer.cpp7
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp13
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h5
-rw-r--r--src/core/hle/service/service.cpp21
-rw-r--r--src/core/hle/service/service.h4
-rw-r--r--src/core/hle/service/sm/sm.cpp44
-rw-r--r--src/core/hle/service/sm/sm.h2
-rw-r--r--src/core/hle/service/sm/sm_controller.cpp41
-rw-r--r--src/core/hle/service/vi/display/vi_display.h6
-rw-r--r--src/core/hle/service/vi/vi.cpp8
-rw-r--r--src/core/internal_network/socket_proxy.cpp4
-rw-r--r--src/input_common/drivers/gc_adapter.cpp6
-rw-r--r--src/input_common/drivers/gc_adapter.h4
-rw-r--r--src/input_common/drivers/sdl_driver.cpp64
-rw-r--r--src/input_common/drivers/sdl_driver.h4
-rw-r--r--src/input_common/input_engine.h7
-rw-r--r--src/input_common/input_poller.cpp6
-rw-r--r--src/shader_recompiler/CMakeLists.txt1
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm.cpp3
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp4
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp16
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_instructions.h3
-rw-r--r--src/shader_recompiler/backend/glasm/glasm_emit_context.cpp4
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp4
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp16
-rw-r--r--src/shader_recompiler/backend/glsl/emit_glsl_instructions.h3
-rw-r--r--src/shader_recompiler/backend/glsl/glsl_emit_context.cpp3
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv.h4
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp4
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp24
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_instructions.h5
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp35
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.h5
-rw-r--r--src/shader_recompiler/environment.h4
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.cpp17
-rw-r--r--src/shader_recompiler/frontend/ir/ir_emitter.h4
-rw-r--r--src/shader_recompiler/frontend/ir/microinstruction.cpp5
-rw-r--r--src/shader_recompiler/frontend/ir/opcodes.h1
-rw-r--r--src/shader_recompiler/frontend/ir/opcodes.inc3
-rw-r--r--src/shader_recompiler/frontend/ir/patch.h4
-rw-r--r--src/shader_recompiler/frontend/ir/type.h31
-rw-r--r--src/shader_recompiler/frontend/ir/value.cpp3
-rw-r--r--src/shader_recompiler/frontend/ir/value.h16
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp3
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate_program.cpp4
-rw-r--r--src/shader_recompiler/host_translate_info.h1
-rw-r--r--src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp3
-rw-r--r--src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp98
-rw-r--r--src/shader_recompiler/ir_opt/passes.h7
-rw-r--r--src/shader_recompiler/ir_opt/position_pass.cpp77
-rw-r--r--src/shader_recompiler/ir_opt/texture_pass.cpp51
-rw-r--r--src/shader_recompiler/shader_info.h12
-rw-r--r--src/tests/video_core/buffer_base.cpp2
-rw-r--r--src/video_core/CMakeLists.txt15
-rw-r--r--src/video_core/engines/maxwell_3d.cpp261
-rw-r--r--src/video_core/engines/maxwell_3d.h52
-rw-r--r--src/video_core/macro/macro.cpp3
-rw-r--r--src/video_core/macro/macro_hle.cpp47
-rw-r--r--src/video_core/macro/macro_interpreter.cpp2
-rw-r--r--src/video_core/macro/macro_jit_x64.cpp2
-rw-r--r--src/video_core/memory_manager.cpp2
-rw-r--r--src/video_core/rasterizer_interface.h2
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_graphics_pipeline.cpp11
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp27
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h4
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp6
-rw-r--r--src/video_core/renderer_opengl/gl_state_tracker.cpp4
-rw-r--r--src/video_core/renderer_vulkan/pipeline_helper.h10
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp22
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp69
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_state_tracker.cpp2
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp27
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h11
-rw-r--r--src/video_core/shader_environment.cpp104
-rw-r--r--src/video_core/shader_environment.h21
-rw-r--r--src/video_core/texture_cache/util.cpp1
-rw-r--r--src/yuzu/CMakeLists.txt4
-rw-r--r--src/yuzu/bootmanager.cpp337
-rw-r--r--src/yuzu/configuration/config.cpp6
-rw-r--r--src/yuzu/configuration/configure_ui.cpp9
-rw-r--r--src/yuzu/configuration/configure_ui.ui23
-rw-r--r--src/yuzu/game_list.cpp4
-rw-r--r--src/yuzu/main.cpp74
-rw-r--r--src/yuzu/main.h13
-rw-r--r--src/yuzu/main.ui1
-rw-r--r--src/yuzu/uisettings.h7
202 files changed, 12657 insertions, 2135 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b625743ea..1d13dc74e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -133,13 +133,13 @@ if (NOT ENABLE_GENERIC)
if (MSVC)
detect_architecture("_M_AMD64" x86_64)
detect_architecture("_M_IX86" x86)
- detect_architecture("_M_ARM" ARM)
- detect_architecture("_M_ARM64" ARM64)
+ detect_architecture("_M_ARM" arm)
+ detect_architecture("_M_ARM64" arm64)
else()
detect_architecture("__x86_64__" x86_64)
detect_architecture("__i386__" x86)
- detect_architecture("__arm__" ARM)
- detect_architecture("__aarch64__" ARM64)
+ detect_architecture("__arm__" arm)
+ detect_architecture("__aarch64__" arm64)
endif()
endif()
@@ -218,11 +218,11 @@ if(ENABLE_QT)
set(QT_VERSION 5.15)
# Check for system Qt on Linux, fallback to bundled Qt
- if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+ if (UNIX AND NOT APPLE)
if (NOT YUZU_USE_BUNDLED_QT)
find_package(Qt5 ${QT_VERSION} COMPONENTS Widgets DBus Multimedia)
endif()
- if (NOT Qt5_FOUND OR YUZU_USE_BUNDLED_QT)
+ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND (NOT Qt5_FOUND OR YUZU_USE_BUNDLED_QT))
# Check for dependencies, then enable bundled Qt download
# Check that the system GLIBCXX version is compatible
@@ -323,7 +323,7 @@ if(ENABLE_QT)
set(YUZU_QT_NO_CMAKE_SYSTEM_PATH "NO_CMAKE_SYSTEM_PATH")
endif()
- if ((${CMAKE_SYSTEM_NAME} STREQUAL "Linux") AND YUZU_USE_BUNDLED_QT)
+ if (UNIX AND NOT APPLE AND YUZU_USE_BUNDLED_QT)
find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets Concurrent Multimedia DBus ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
else()
find_package(Qt5 ${QT_VERSION} REQUIRED COMPONENTS Widgets Concurrent Multimedia ${QT_PREFIX_HINT} ${YUZU_QT_NO_CMAKE_SYSTEM_PATH})
diff --git a/dist/languages/uk.ts b/dist/languages/uk.ts
new file mode 100644
index 000000000..66a3ac96e
--- /dev/null
+++ b/dist/languages/uk.ts
@@ -0,0 +1,7321 @@
+<?xml version="1.0" ?><!DOCTYPE TS><TS version="2.1" language="uk" sourcelanguage="en_US">
+<context>
+ <name>AboutDialog</name>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="14"/>
+ <source>About yuzu</source>
+ <translation>Про yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="72"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="85"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 (%2)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 (%2)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="98"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv3.0+.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Ubuntu&apos;; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;yuzu є експериментальним емулятором Nintendo Switch з відкритим кодом ліцензований під GPLv3.0+.&lt;/span&gt;&lt;/p&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;MS Shell Dlg 2&apos;; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
+&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:12pt;&quot;&gt;Це програмне забезпечення не слід використовувати для ігор, які ви отримали незаконним шляхом.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/LICENSE.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Веб-сайт&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Вихідний код&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Вкладники&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/LICENSE.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Ліцензія&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/aboutdialog.ui" line="146"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; є торговою маркою Nintendo. yuzu не пов&apos;язаний з Nintendo у будь-якому вигляді.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>CalibrationConfigurationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="23"/>
+ <source>Communicating with the server...</source>
+ <translation>Зв&apos;язок із сервером...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="24"/>
+ <source>Cancel</source>
+ <translation>Скасувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="43"/>
+ <source>Touch the top left corner &lt;br&gt;of your touchpad.</source>
+ <translation>Торкніться верхнього лівого кута &lt;br&gt; вашого тачпаду.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="46"/>
+ <source>Now touch the bottom right corner &lt;br&gt;of your touchpad.</source>
+ <translation>Тепер торкніться правого нижнього кута &lt;br&gt; вашого тачпаду.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="49"/>
+ <source>Configuration completed!</source>
+ <translation>Налаштування завершено!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="57"/>
+ <source>OK</source>
+ <translation>ОК</translation>
+ </message>
+</context>
+<context>
+ <name>ChatRoom</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.ui" line="14"/>
+ <source>Room Window</source>
+ <translation>Вікно кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.ui" line="40"/>
+ <source>Send Chat Message</source>
+ <translation>Надіслати повідомлення в чат</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.ui" line="47"/>
+ <source>Send Message</source>
+ <translation>Надіслати повідомлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="181"/>
+ <source>Members</source>
+ <translation>Члени</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="318"/>
+ <source>%1 has joined</source>
+ <translation>%1 приєднався</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="321"/>
+ <source>%1 has left</source>
+ <translation>%1 вийшов</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="324"/>
+ <source>%1 has been kicked</source>
+ <translation>%1 вигнано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="327"/>
+ <source>%1 has been banned</source>
+ <translation>%1 заблоковано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="330"/>
+ <source>%1 has been unbanned</source>
+ <translation>%1 розблоковано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="446"/>
+ <source>View Profile</source>
+ <translation>Переглянути профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="459"/>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="469"/>
+ <source>Block Player</source>
+ <translation>Заблокувати гравця</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="470"/>
+ <source>When you block a player, you will no longer receive chat messages from them.&lt;br&gt;&lt;br&gt;Are you sure you would like to block %1?</source>
+ <translation>Коли ви блокуєте гравця, ви більше не отримуватиме від нього повідомлення у чаті. &lt;br&gt;&lt;br&gt;Ви впевнені що бажаєте заблокувати %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="483"/>
+ <source>Kick</source>
+ <translation>Вигнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="484"/>
+ <source>Ban</source>
+ <translation>Заблокувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="488"/>
+ <source>Kick Player</source>
+ <translation>Вигнати гравця</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="489"/>
+ <source>Are you sure you would like to &lt;b&gt;kick&lt;/b&gt; %1?</source>
+ <translation>Ви впевнені що бажаєте &lt;b&gt;вигнати&lt;/b&gt; %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="497"/>
+ <source>Ban Player</source>
+ <translation>Заблокувати гравця</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="498"/>
+ <source>Are you sure you would like to &lt;b&gt;kick and ban&lt;/b&gt; %1?
+
+This would ban both their forum username and their IP address.</source>
+ <translation>Ви впевнені що бажаєте &lt;b&gt;вигнати і заблокувати&lt;/b&gt; %1?
+
+Ця дія заблокує ім&apos;я користувача на форумі та IP-адресу.</translation>
+ </message>
+</context>
+<context>
+ <name>ClientRoom</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.ui" line="14"/>
+ <source>Room Window</source>
+ <translation>Вікно кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.ui" line="27"/>
+ <source>Room Description</source>
+ <translation>Опис кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.ui" line="47"/>
+ <source>Moderation...</source>
+ <translation>Модерація...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.ui" line="57"/>
+ <source>Leave Room</source>
+ <translation>Залишити кімнату</translation>
+ </message>
+</context>
+<context>
+ <name>ClientRoomWindow</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.cpp" line="78"/>
+ <source>Connected</source>
+ <translation>З&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.cpp" line="87"/>
+ <source>Disconnected</source>
+ <translation>Роз&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/client_room.cpp" line="100"/>
+ <source>%1 - %2 (%3/%4 members) - connected</source>
+ <translation>%1 - %2 (%3/%4 члени) - з&apos;єднано</translation>
+ </message>
+</context>
+<context>
+ <name>CompatDB</name>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="20"/>
+ <source>Report Compatibility</source>
+ <translation>Повідомити про сумісність</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="27"/>
+ <location filename="../../src/yuzu/compatdb.ui" line="63"/>
+ <source>Report Game Compatibility</source>
+ <translation>Повідомити про сумісність гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="36"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Якщо ви бажаєте надіслати звіт до &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;списку сумісності yuzu&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, наступна інформація буде зібрана та відображена на сайті:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Інформація про залізо (ЦП / ГП / Операційна система)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Версія yuzu&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Підключений акаунт yuzu&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="72"/>
+ <source>Perfect</source>
+ <translation>Ідеально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="79"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Гра працює ідеально, без звукових чи графічних артефактів.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="89"/>
+ <source>Great</source>
+ <translation>Чудово</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="96"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Гра працює з невеликими графічними або звуковими артефактами та може бути пройдена від початку до кінця. Можуть знадобитися обхідні шляхи.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="106"/>
+ <source>Okay</source>
+ <translation>Добре</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="113"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Гра працює зі суттєвими графічними або звуковими артефактами, але з обхідними шляхами може бути пройдена.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="123"/>
+ <source>Bad</source>
+ <translation>Погано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="130"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Гра працює, але з суттєвими графічними чи звуковими артефактами. У деяких місцях неможливо пройти навіть із обхідними шляхами.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="140"/>
+ <source>Intro/Menu</source>
+ <translation>Вступ/Меню</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="147"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;У гру неможливо грати через серйозні графічні або звукові артефакти. Неможливо просунутися далі за стартове меню.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="157"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Не запускається</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="170"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Гра вилітає при спробі запуску.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="182"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Незалежно від швидкості або продуктивності, наскільки добре ця гра працює від початку до кінця у поточній версії yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.ui" line="206"/>
+ <source>Thank you for your submission!</source>
+ <translation>Дякуємо за ваш звіт!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="58"/>
+ <source>Submitting</source>
+ <translation>Надсилання</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="71"/>
+ <source>Communication error</source>
+ <translation>Помилка з&apos;єднання</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="72"/>
+ <source>An error occurred while sending the Testcase</source>
+ <translation>Сталася помилка під час надсилання звіту</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/compatdb.cpp" line="74"/>
+ <source>Next</source>
+ <translation>Далі</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureAudio</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="14"/>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="20"/>
+ <source>Audio</source>
+ <translation>Аудіо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="28"/>
+ <source>Output Engine:</source>
+ <translation>Рушій виводу:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="42"/>
+ <source>Output Device</source>
+ <translation>Пристрій виводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="56"/>
+ <source>Input Device</source>
+ <translation>Пристрій вводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="84"/>
+ <source>Use global volume</source>
+ <translation>Використовувати загальну гучність</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="89"/>
+ <source>Set volume:</source>
+ <translation>Встановити гучність:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="97"/>
+ <source>Volume:</source>
+ <translation>Гучність</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.ui" line="142"/>
+ <source>0 %</source>
+ <translation>0 %</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_audio.cpp" line="108"/>
+ <source>%1%</source>
+ <comment>Volume percentage (e.g. 50%)</comment>
+ <translation>%1%</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCamera</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="14"/>
+ <source>Configure Infrared Camera</source>
+ <translation>Налаштування інфрачервоної камери</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="26"/>
+ <source>Select where the image of the emulated camera comes from. It may be a virtual camera or a real camera.</source>
+ <translation>Виберіть, звідки береться зображення емульованої камери. Це може бути віртуальна або реальна камера.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="52"/>
+ <source>Camera Image Source:</source>
+ <translation>Джерело зображення камери:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="71"/>
+ <source>Input device:</source>
+ <translation>Пристрій вводу:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="96"/>
+ <source>Preview</source>
+ <translation>Попередній перегляд</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="108"/>
+ <source>Resolution: 320*240</source>
+ <translation>Роздільна здатність: 320*240</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="115"/>
+ <source>Click to preview</source>
+ <translation>Натисніть для попереднього перегляду</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.ui" line="140"/>
+ <source>Restore Defaults</source>
+ <translation>Значення за замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_camera.cpp" line="135"/>
+ <source>Auto</source>
+ <translation>Авто</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpu</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="17"/>
+ <source>CPU</source>
+ <translation>ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="25"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="34"/>
+ <source>Accuracy:</source>
+ <translation>Точність:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="42"/>
+ <source>Auto</source>
+ <translation>Авто</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="47"/>
+ <source>Accurate</source>
+ <translation>Точно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="52"/>
+ <source>Unsafe</source>
+ <translation>Небезпечно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="57"/>
+ <source>Paranoid (disables most optimizations)</source>
+ <translation>Параноїк (відключає більшість оптимізацій)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="68"/>
+ <source>We recommend setting accuracy to &quot;Auto&quot;.</source>
+ <translation>Ми рекомендуємо встановити точність на &quot;Авто&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="85"/>
+ <source>Unsafe CPU Optimization Settings</source>
+ <translation>Небезпечні налаштування оптимізації ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="91"/>
+ <source>These settings reduce accuracy for speed.</source>
+ <translation>Ці налаштування зменшують точність заради швидкості. </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="101"/>
+ <source>
+ &lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="106"/>
+ <source>Unfuse FMA (improve performance on CPUs without FMA)</source>
+ <translation>Не використовувати FMA (покращує продуктивність на ЦП без FMA)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="113"/>
+ <source>
+ &lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="118"/>
+ <source>Faster FRSQRTE and FRECPE</source>
+ <translation>Прискорені FRSQRTE та FRECPE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="125"/>
+ <source>
+ &lt;div&gt;This option improves the speed of 32 bits ASIMD floating-point functions by running with incorrect rounding modes.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="130"/>
+ <source>Faster ASIMD instructions (32 bits only)</source>
+ <translation>Швидші інструкції ASIMD (лише 32 біт)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="137"/>
+ <source>
+ &lt;div&gt;This option improves speed by removing NaN checking. Please note this also reduces accuracy of certain floating-point instructions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="142"/>
+ <source>Inaccurate NaN handling</source>
+ <translation>Неправильна обробка NaN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="149"/>
+ <source>
+ &lt;div&gt;This option improves speed by eliminating a safety check before every memory read/write in guest. Disabling it may allow a game to read/write the emulator's memory.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="154"/>
+ <source>Disable address space checks</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="161"/>
+ <source>
+ &lt;div&gt;This option improves speed by relying only on the semantics of cmpxchg to ensure safety of exclusive access instructions. Please note this may result in deadlocks and other race conditions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="166"/>
+ <source>Ignore global monitor</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu.ui" line="191"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Налаштування ЦП недоступні, поки запущена гра.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureCpuDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="17"/>
+ <source>CPU</source>
+ <translation>ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="25"/>
+ <source>Toggle CPU Optimizations</source>
+ <translation>Увімкнути оптимізації ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="31"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;For debugging only.&lt;/span&gt;&lt;br/&gt;If you&apos;re not sure what these do, keep all of these enabled. &lt;br/&gt;These settings, when disabled, only take effect when CPU Debugging is enabled. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Тільки для налагодження.&lt;/span&gt;&lt;br/&gt;Якщо ви не впевнені в тому, що вони роблять, залиште всі ці параметри увімкненими. &lt;br/&gt;Коли їх вимкнено, ці параметри набувають чинності лише за увімкненого налагодження ЦП. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="41"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="48"/>
+ <source>Enable inline page tables</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="55"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="60"/>
+ <source>Enable block linking</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="67"/>
+ <source>
+ &lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="72"/>
+ <source>Enable return stack buffer</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="79"/>
+ <source>
+ &lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="84"/>
+ <source>Enable fast dispatcher</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="91"/>
+ <source>
+ &lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="96"/>
+ <source>Enable context elimination</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="103"/>
+ <source>
+ &lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="108"/>
+ <source>Enable constant propagation</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="115"/>
+ <source>
+ &lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="120"/>
+ <source>Enable miscellaneous optimizations</source>
+ <translation>Увімкнути різні оптимізації</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="127"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="133"/>
+ <source>Enable misalignment check reduction</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="140"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all memory accesses to use Software MMU Emulation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="147"/>
+ <source>Enable Host MMU Emulation (general memory instructions)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="154"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up exclusive memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it causes guest exclusive memory reads/writes to be done directly into memory and make use of Host's MMU.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Disabling this forces all exclusive memory accesses to use Software MMU Emulation.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="161"/>
+ <source>Enable Host MMU Emulation (exclusive memory instructions)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="168"/>
+ <source>
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;This optimization speeds up exclusive memory accesses by the guest program.&lt;/div&gt;
+ &lt;div style=&quot;white-space: nowrap&quot;&gt;Enabling it reduces the overhead of fastmem failure of exclusive memory accesses.&lt;/div&gt;
+ </source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="174"/>
+ <source>Enable recompilation of exclusive memory instructions</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_cpu_debug.ui" line="199"/>
+ <source>CPU settings are available only when game is not running.</source>
+ <translation>Налаштування ЦП доступні тільки тоді, коли гру не запущено.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebug</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="15"/>
+ <source>Debugger</source>
+ <translation>Налагоджувач</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="23"/>
+ <source>Enable GDB Stub</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="43"/>
+ <source>Port:</source>
+ <translation>Порт:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="67"/>
+ <source>Logging</source>
+ <translation>Журналювання</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="75"/>
+ <source>Global Log Filter</source>
+ <translation>Глобальний фільтр журналів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="87"/>
+ <source>Show Log in Console</source>
+ <translation>Показувати журнал у консолі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="94"/>
+ <source>Open Log Location</source>
+ <translation>Відкрити папку для журналів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="104"/>
+ <source>When checked, the max size of the log increases from 100 MB to 1 GB</source>
+ <translation>Якщо увімкнено, максимальний розмір журналу збільшується зі 100 МБ до 1 ГБ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="107"/>
+ <source>Enable Extended Logging**</source>
+ <translation>Увімкнути розширене ведення журналу**</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="117"/>
+ <source>Homebrew</source>
+ <translation>Homebrew</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="125"/>
+ <source>Arguments String</source>
+ <translation>Рядок аргументів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="140"/>
+ <source>Graphics</source>
+ <translation>Графіка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="149"/>
+ <source>When checked, the graphics API enters a slower debugging mode</source>
+ <translation>Якщо увімкнено, графічний API переходить у повільніший режим налагодження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="152"/>
+ <source>Enable Graphics Debugging</source>
+ <translation>Увімкнути налагодження графіки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="159"/>
+ <source>When checked, it enables Nsight Aftermath crash dumps</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="162"/>
+ <source>Enable Nsight Aftermath</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="172"/>
+ <source>When checked, it will dump all the original assembler shaders from the disk shader cache or game as found</source>
+ <translation>Якщо ввімкнено, буде дампити всі оригінальні шейдери асемблера з кешу шейдерів на диску або гри як знайдені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="175"/>
+ <source>Dump Game Shaders</source>
+ <translation>Дамп ігрових шейдерів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="185"/>
+ <source>When checked, it will dump all the macro programs of the GPU</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="188"/>
+ <source>Dump Maxwell Macros</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="198"/>
+ <source>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="201"/>
+ <source>Disable Macro JIT</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="208"/>
+ <source>When checked, yuzu will log statistics about the compiled pipeline cache</source>
+ <translation>Якщо увімкнено, yuzu записуватиме статистику про скомпільований кеш конвеєра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="211"/>
+ <source>Enable Shader Feedback</source>
+ <translation>Увімкнути зворотний зв&apos;язок про шейдери</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="218"/>
+ <source>When checked, it executes shaders without loop logic changes</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="221"/>
+ <source>Disable Loop safety checks</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="231"/>
+ <source>Debugging</source>
+ <translation>Налагодження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="237"/>
+ <source>Enable Verbose Reporting Services**</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="244"/>
+ <source>Enable FS Access Log</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="251"/>
+ <source>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="254"/>
+ <source>Dump Audio Commands To Console**</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="261"/>
+ <source>Create Minidump After Crash</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="271"/>
+ <source>Advanced</source>
+ <translation>Розширені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="277"/>
+ <source>Kiosk (Quest) Mode</source>
+ <translation>Режим кіоску (Квест)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="284"/>
+ <source>Enable CPU Debugging</source>
+ <translation>Увімкнути налагодження ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="291"/>
+ <source>Enable Debug Asserts</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="298"/>
+ <source>Enable Auto-Stub**</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="305"/>
+ <source>Enable All Controller Types</source>
+ <translation>Увімкнути всі типи контролерів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="312"/>
+ <source>Disable Web Applet</source>
+ <translation>Вимкнути веб-аплет</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="319"/>
+ <source>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</source>
+ <translation>Дозволяє yuzu перевіряти наявність робочого середовища Vulkan під час запуску програми. Вимкніть цю опцію, якщо це викликає проблеми з тим, що зовнішні програми бачать yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="322"/>
+ <source>Perform Startup Vulkan Check</source>
+ <translation>Виконувати перевірку Vulkan під час запуску</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.ui" line="337"/>
+ <source>**This will be reset automatically when yuzu closes.</source>
+ <translation>**Це буде автоматично скинуто після закриття yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="35"/>
+ <source>Restart Required</source>
+ <translation>Потрібен перезапуск</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="36"/>
+ <source>yuzu is required to restart in order to apply this setting.</source>
+ <translation>yuzu потрібно перезапустити, щоб застосувати це налаштування.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="86"/>
+ <source>Web applet not compiled</source>
+ <translation>Веб-аплет не скомпільовано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug.cpp" line="93"/>
+ <source>MiniDump creation not compiled</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="14"/>
+ <source>Configure Debug Controller</source>
+ <translation>Налаштування налагоджувального контролера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_controller.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>За замовчуванням</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDebugTab</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_tab.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_tab.ui" line="17"/>
+ <location filename="../../src/yuzu/configuration/configure_debug_tab.cpp" line="16"/>
+ <source>Debug</source>
+ <translation>Налагодження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_debug_tab.cpp" line="17"/>
+ <source>CPU</source>
+ <translation>ЦП</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure.ui" line="20"/>
+ <source>yuzu Configuration</source>
+ <translation>Налаштування yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="52"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="156"/>
+ <source>Audio</source>
+ <translation>Аудіо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="53"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="154"/>
+ <source>CPU</source>
+ <translation>ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="54"/>
+ <source>Debug</source>
+ <translation>Налагодження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="55"/>
+ <source>Filesystem</source>
+ <translation>Файлова система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="150"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="57"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="155"/>
+ <source>Graphics</source>
+ <translation>Графіка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="58"/>
+ <source>GraphicsAdvanced</source>
+ <translation>ГрафікаРозширені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="59"/>
+ <source>Hotkeys</source>
+ <translation>Гарячі клавіші</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="60"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="157"/>
+ <source>Controls</source>
+ <translation>Керування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="61"/>
+ <source>Profiles</source>
+ <translation>Профілі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="62"/>
+ <source>Network</source>
+ <translation>Мережа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="152"/>
+ <source>System</source>
+ <translation>Система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="64"/>
+ <source>Game List</source>
+ <translation>Список ігор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_dialog.cpp" line="65"/>
+ <source>Web</source>
+ <translation>Мережа</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureFilesystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="17"/>
+ <source>Filesystem</source>
+ <translation>Файлова система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="25"/>
+ <source>Storage Directories</source>
+ <translation>Каталоги зберігання</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="31"/>
+ <source>NAND</source>
+ <translation>NAND</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="38"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="114"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="136"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="143"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="51"/>
+ <source>SD Card</source>
+ <translation>SD карта</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="84"/>
+ <source>Gamecard</source>
+ <translation>Картридж</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="90"/>
+ <source>Path</source>
+ <translation>Шлях</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="100"/>
+ <source>Inserted</source>
+ <translation>Вставлений</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="107"/>
+ <source>Current Game</source>
+ <translation>Поточна гра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="124"/>
+ <source>Patch Manager</source>
+ <translation>Керування патчами</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="152"/>
+ <source>Dump Decompressed NSOs</source>
+ <translation>Дамп розпакованих NSO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="159"/>
+ <source>Dump ExeFS</source>
+ <translation>Дамп ExeFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="168"/>
+ <source>Mod Load Root</source>
+ <translation>Папка з модами</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="175"/>
+ <source>Dump Root</source>
+ <translation>Корінь дампу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="201"/>
+ <source>Caching</source>
+ <translation>Кешування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="209"/>
+ <source>Cache Game List Metadata</source>
+ <translation>Кешувати метадані списку ігор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.ui" line="216"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="131"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="135"/>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="140"/>
+ <source>Reset Metadata Cache</source>
+ <translation>Скинути кеш метаданих</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="93"/>
+ <source>Select Emulated NAND Directory...</source>
+ <translation>Виберіть папку для емульованого NAND...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="96"/>
+ <source>Select Emulated SD Directory...</source>
+ <translation>Виберіть папку для емульованого SD...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="99"/>
+ <source>Select Gamecard Path...</source>
+ <translation>Оберіть папку для картриджів...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="102"/>
+ <source>Select Dump Directory...</source>
+ <translation>Оберіть папку для дампів...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="105"/>
+ <source>Select Mod Load Directory...</source>
+ <translation>Оберіть папку для модів...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="132"/>
+ <source>The metadata cache is already empty.</source>
+ <translation>Кеш метаданих вже порожній.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="136"/>
+ <source>The operation completed successfully.</source>
+ <translation>Операція завершилася успішно.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_filesystem.cpp" line="141"/>
+ <source>The metadata cache couldn&apos;t be deleted. It might be in use or non-existent.</source>
+ <translation>Кеш метаданих не можна видалити. Можливо, він використовується або відсутній.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGeneral</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="17"/>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="25"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="35"/>
+ <source>Limit Speed Percent</source>
+ <translation>Обмеження відсотка швидкості</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="42"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="60"/>
+ <source>Multicore CPU Emulation</source>
+ <translation>Багатоядерна емуляція ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="67"/>
+ <source>Extended memory layout (6GB DRAM)</source>
+ <translation>Розширене компонування пам&apos;яті (6 ГБ DRAM)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="74"/>
+ <source>Confirm exit while emulation is running</source>
+ <translation>Підтверджувати вихід під час емуляції</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="81"/>
+ <source>Prompt for user on game boot</source>
+ <translation>Запитувати користувача під час запуску гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="88"/>
+ <source>Pause emulation when in background</source>
+ <translation>Призупиняти емуляцію у фоновому режимі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="95"/>
+ <source>Mute audio when in background</source>
+ <translation>Приглушити звук у фоновому режимі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="102"/>
+ <source>Hide mouse on inactivity</source>
+ <translation>Приховування миші при бездіяльності</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.ui" line="144"/>
+ <source>Reset All Settings</source>
+ <translation>Скинути всі налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.cpp" line="68"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_general.cpp" line="69"/>
+ <source>This reset all settings and remove all per-game configurations. This will not delete game directories, profiles, or input profiles. Proceed?</source>
+ <translation>Це скине всі налаштування і видалить усі конфігурації під окремі ігри. При цьому не будуть видалені шляхи до ігор, профілів або профілів вводу. Продовжити?</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphics</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="17"/>
+ <source>Graphics</source>
+ <translation>Графіка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="25"/>
+ <source>API Settings</source>
+ <translation>Налаштування API</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="64"/>
+ <source>Shader Backend:</source>
+ <translation>Бекенд шейдерів:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="92"/>
+ <source>Device:</source>
+ <translation>Пристрій:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="120"/>
+ <source>API:</source>
+ <translation>API:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="156"/>
+ <source>Graphics Settings</source>
+ <translation>Налаштування графіки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="162"/>
+ <source>Use disk pipeline cache</source>
+ <translation>Використовувати кеш конвеєра на диску</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="169"/>
+ <source>Use asynchronous GPU emulation</source>
+ <translation>Використовувати асинхронну емуляцію ГП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="176"/>
+ <source>Accelerate ASTC texture decoding</source>
+ <translation>Прискорення декодування текстур ASTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="198"/>
+ <source>NVDEC emulation:</source>
+ <translation>Емуляція NVDEC:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="206"/>
+ <source>No Video Output</source>
+ <translation>Відсутність відеовиходу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="211"/>
+ <source>CPU Video Decoding</source>
+ <translation>Декодування відео на ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="216"/>
+ <source>GPU Video Decoding (Default)</source>
+ <translation>Декодування відео на ГП (за замовчуванням)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="242"/>
+ <source>Fullscreen Mode:</source>
+ <translation>Повноекранний режим:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="250"/>
+ <source>Borderless Windowed</source>
+ <translation>Вікно без рамок</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="255"/>
+ <source>Exclusive Fullscreen</source>
+ <translation>Ексклюзивний повноекранний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="281"/>
+ <source>Aspect Ratio:</source>
+ <translation>Співвідношення сторін:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="289"/>
+ <source>Default (16:9)</source>
+ <translation>За замовчуванням (16:9)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="294"/>
+ <source>Force 4:3</source>
+ <translation>Змусити 4:3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="299"/>
+ <source>Force 21:9</source>
+ <translation>Змусити 21:9</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="304"/>
+ <source>Force 16:10</source>
+ <translation>Змусити 16:10</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="309"/>
+ <source>Stretch to Window</source>
+ <translation>Розтягнути до вікна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="335"/>
+ <source>Resolution:</source>
+ <translation>Роздільна здатність:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="343"/>
+ <source>0.5X (360p/540p) [EXPERIMENTAL]</source>
+ <translation>0.5X (360p/540p) [ЕКСПЕРИМЕНТАЛЬНЕ]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="348"/>
+ <source>0.75X (540p/810p) [EXPERIMENTAL]</source>
+ <translation>0.75X (540p/810p) [ЕКСПЕРИМЕНТАЛЬНЕ]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="353"/>
+ <source>1X (720p/1080p)</source>
+ <translation>1X (720p/1080p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="358"/>
+ <source>2X (1440p/2160p)</source>
+ <translation>2X (1440p/2160p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="363"/>
+ <source>3X (2160p/3240p)</source>
+ <translation>3X (2160p/3240p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="368"/>
+ <source>4X (2880p/4320p)</source>
+ <translation>4X (2880p/4320p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="373"/>
+ <source>5X (3600p/5400p)</source>
+ <translation>5X (3600p/5400p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="378"/>
+ <source>6X (4320p/6480p)</source>
+ <translation>6X (4320p/6480p)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="404"/>
+ <source>Window Adapting Filter:</source>
+ <translation>Фільтр адаптації вікна:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="412"/>
+ <source>Nearest Neighbor</source>
+ <translation>Найближчий сусід</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="417"/>
+ <source>Bilinear</source>
+ <translation>Білінійне</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="422"/>
+ <source>Bicubic</source>
+ <translation>Бікубічне</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="427"/>
+ <source>Gaussian</source>
+ <translation>Гауса</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="432"/>
+ <source>ScaleForce</source>
+ <translation>ScaleForce</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="437"/>
+ <source>AMD FidelityFX™️ Super Resolution (Vulkan Only)</source>
+ <translation>AMD FidelityFX™️ Super Resolution (Лише для Vulkan)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="463"/>
+ <source>Anti-Aliasing Method:</source>
+ <translation>Метод згладжування:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="471"/>
+ <source>None</source>
+ <translation>Вимкнено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="476"/>
+ <source>FXAA</source>
+ <translation>FXAA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="511"/>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="521"/>
+ <source>Use global background color</source>
+ <translation>Використовувати глобальний фоновий колір</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="526"/>
+ <source>Set background color:</source>
+ <translation>Встановити фоновий колір:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.ui" line="534"/>
+ <source>Background Color:</source>
+ <translation>Фоновий колір:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics.cpp" line="33"/>
+ <source>GLASM (Assembly Shaders, NVIDIA Only)</source>
+ <translation>GLASM (асемблерні шейдери, лише для NVIDIA)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureGraphicsAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="17"/>
+ <source>Advanced</source>
+ <translation>Розширені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="25"/>
+ <source>Advanced Graphics Settings</source>
+ <translation>Розширені налаштування графіки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="46"/>
+ <source>Accuracy Level:</source>
+ <translation>Рівень точності:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="75"/>
+ <source>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don&apos;t notice a performance difference.</source>
+ <translation>Вертикальна синхронізація запобігає розривам екрана, але деякі відеокарти мають нижчу продуктивність при вертикальній синхронізації. Залишайте увімкненим, якщо ви не помічаєте різниці в продуктивності.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="78"/>
+ <source>Use VSync</source>
+ <translation>Використувати вертикальну сінхронізацію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="85"/>
+ <source>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</source>
+ <translation>Вмикає асинхронну компіляцію шейдерів, що зменшить зависання через шейдери. Функція є експериментальною.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="88"/>
+ <source>Use asynchronous shader building (Hack)</source>
+ <translation>Використовувати асинхронну побудову шейдерів (хак)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="95"/>
+ <source>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</source>
+ <translation>Вмикає функцію Fast GPU Time. Цей параметр змусить більшість ігор працювати в максимальній рідній роздільній здатності.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="98"/>
+ <source>Use Fast GPU Time (Hack)</source>
+ <translation>Увімкнути Fast GPU Time (Хак)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="105"/>
+ <source>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</source>
+ <translation>Вмикає песимістичне очищення буферів. Ця опція змушує промивати немодифіковані буфери, що може знизити продуктивність.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="108"/>
+ <source>Use pessimistic buffer flushes (Hack)</source>
+ <translation>Використовувати песимістичне очищення буферів (Хак)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="130"/>
+ <source>Anisotropic Filtering:</source>
+ <translation>Анізотропна фільтрація:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="138"/>
+ <source>Automatic</source>
+ <translation>Автоматично</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="143"/>
+ <source>Default</source>
+ <translation>За замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="148"/>
+ <source>2x</source>
+ <translation>2x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="153"/>
+ <source>4x</source>
+ <translation>4x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="158"/>
+ <source>8x</source>
+ <translation>8x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_graphics_advanced.ui" line="163"/>
+ <source>16x</source>
+ <translation>16x</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureHotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="14"/>
+ <source>Hotkey Settings</source>
+ <translation>Налаштування гарячих клавіш</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="17"/>
+ <source>Hotkeys</source>
+ <translation>Гарячі клавіші</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="25"/>
+ <source>Double-click on a binding to change it.</source>
+ <translation>Натисніть двічі на прив&apos;язці, щоб змінити її.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="45"/>
+ <source>Clear All</source>
+ <translation>Очистити все</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.ui" line="52"/>
+ <source>Restore Defaults</source>
+ <translation>Відновити значення за замовчуванням.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="98"/>
+ <source>Action</source>
+ <translation>Дія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="98"/>
+ <source>Hotkey</source>
+ <translation>Гаряча клавіша</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="98"/>
+ <source>Controller Hotkey</source>
+ <translation>Гаряча клавіша контролера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="138"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="379"/>
+ <source>Conflicting Key Sequence</source>
+ <translation>Конфліктуюча комбінація клавіш</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="139"/>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="165"/>
+ <source>The entered key sequence is already assigned to: %1</source>
+ <translation>Введена комбінація вже призначена до: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="158"/>
+ <source>Home+%1</source>
+ <translation>Home+%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="172"/>
+ <source>[waiting]</source>
+ <translation>[очікування]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="242"/>
+ <source>Invalid</source>
+ <translation>Неприпустимо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="343"/>
+ <source>Restore Default</source>
+ <translation>Відновити значення за замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="344"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="365"/>
+ <source>Conflicting Button Sequence</source>
+ <translation>Конфліктуюче поєднання кнопок</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="366"/>
+ <source>The default button sequence is already assigned to: %1</source>
+ <translation>Типова комбінація кнопок вже призначена до: %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_hotkeys.cpp" line="380"/>
+ <source>The default key sequence is already assigned to: %1</source>
+ <translation>Типова комбінація клавіш вже призначена до: %1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInput</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="14"/>
+ <source>ConfigureInput</source>
+ <translation>НалаштуванняВводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="39"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="42"/>
+ <source>Player 1</source>
+ <translation>Гравець 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="50"/>
+ <source>Player 2</source>
+ <translation>Гравець 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="58"/>
+ <source>Player 3</source>
+ <translation>Гравець 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="66"/>
+ <source>Player 4</source>
+ <translation>Гравець 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="74"/>
+ <source>Player 5</source>
+ <translation>Гравець 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="82"/>
+ <source>Player 6</source>
+ <translation>Гравець 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="90"/>
+ <source>Player 7</source>
+ <translation>Гравець 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="95"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="98"/>
+ <source>Player 8</source>
+ <translation>Гравець 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="103"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="106"/>
+ <source>Advanced</source>
+ <translation>Розширені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="138"/>
+ <source>Console Mode</source>
+ <translation>Режим консолі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="159"/>
+ <source>Docked</source>
+ <translation>У док-станції</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="169"/>
+ <source>Handheld</source>
+ <translation>Портативний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="179"/>
+ <source>Vibration</source>
+ <translation>Вібрація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="215"/>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="261"/>
+ <source>Configure</source>
+ <translation>Налаштувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="225"/>
+ <source>Motion</source>
+ <translation>Рух</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="296"/>
+ <source>Controllers</source>
+ <translation>Контролери</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="324"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="365"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="375"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="385"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="395"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="405"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="415"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="425"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="435"/>
+ <source>Connected</source>
+ <translation>З&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="494"/>
+ <source>Defaults</source>
+ <translation>За замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input.ui" line="537"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Налаштування вводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="74"/>
+ <source>Joycon Colors</source>
+ <translation>Кольори Joy-Con&apos;ів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="125"/>
+ <source>Player 1</source>
+ <translation>Гравець 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="164"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="450"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1040"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1365"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1651"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1955"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2241"/>
+ <source>L Body</source>
+ <translation>Лівий контролер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="219"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="505"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="809"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1095"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1420"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1706"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2010"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2296"/>
+ <source>L Button</source>
+ <translation>Кнопка L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="295"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="581"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="885"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1171"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1496"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1782"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2086"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2372"/>
+ <source>R Body</source>
+ <translation>Правий контролер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="350"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="636"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="940"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1226"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1551"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1837"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2141"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2427"/>
+ <source>R Button</source>
+ <translation>Кнопка R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="411"/>
+ <source>Player 2</source>
+ <translation>Гравець 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="715"/>
+ <source>Player 3</source>
+ <translation>Гравець 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1001"/>
+ <source>Player 4</source>
+ <translation>Гравець 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1326"/>
+ <source>Player 5</source>
+ <translation>Гравець 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1612"/>
+ <source>Player 6</source>
+ <translation>Гравець 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="1916"/>
+ <source>Player 7</source>
+ <translation>Гравець 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2202"/>
+ <source>Player 8</source>
+ <translation>Гравець 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2533"/>
+ <source>Emulated Devices</source>
+ <translation>Емульовані пристрої</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2545"/>
+ <source>Keyboard</source>
+ <translation>Клавіатура</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2558"/>
+ <source>Mouse</source>
+ <translation>Миша</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2565"/>
+ <source>Touchscreen</source>
+ <translation>Сенсорний екран</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2588"/>
+ <source>Advanced</source>
+ <translation>Розширені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2595"/>
+ <source>Debug Controller</source>
+ <translation>Налагоджувальний контролер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2602"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2616"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2630"/>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2743"/>
+ <source>Configure</source>
+ <translation>Налаштувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2609"/>
+ <source>Ring Controller</source>
+ <translation>Контролер Ring</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2623"/>
+ <source>Infrared Camera</source>
+ <translation>Інфрачервона камера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2640"/>
+ <source>Other</source>
+ <translation>Інше</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2652"/>
+ <source>Emulate Analog with Keyboard Input</source>
+ <translation>Емуляція аналогового вводу з клавіатури</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2659"/>
+ <source>Requires restarting yuzu</source>
+ <translation>Потребує перезапуску yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2668"/>
+ <source>Enable XInput 8 player support (disables web applet)</source>
+ <translation>Увімкнути підтримку 8-ми гравців на XInput (відключає веб-аплет)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2681"/>
+ <source>Enable UDP controllers (not needed for motion)</source>
+ <translation>Увімкнути UDP контролери (не обов&apos;язково для руху)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2694"/>
+ <source>Controller navigation</source>
+ <translation>Навігація контролера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2707"/>
+ <source>Enable mouse panning</source>
+ <translation>Увімкнути панорамування миші</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2714"/>
+ <source>Mouse sensitivity</source>
+ <translation>Чутливість миші</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2720"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_advanced.ui" line="2736"/>
+ <source>Motion / Touch</source>
+ <translation>Рух і сенсор</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputPlayer</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="14"/>
+ <source>Configure Input</source>
+ <translation>Налаштування вводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="63"/>
+ <source>Connect Controller</source>
+ <translation>Підключити контролер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="100"/>
+ <source>Input Device</source>
+ <translation>Пристрій вводу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="156"/>
+ <source>Profile</source>
+ <translation>Профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="196"/>
+ <source>Save</source>
+ <translation>Зберегти</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="212"/>
+ <source>New</source>
+ <translation>Новий</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="228"/>
+ <source>Delete</source>
+ <translation>Видалити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="291"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1291"/>
+ <source>Left Stick</source>
+ <translation>Лівий міні-джойстик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="349"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="391"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="925"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="964"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2583"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2622"/>
+ <source>Up</source>
+ <translation>Вгору</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="422"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="461"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="995"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1034"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2069"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2653"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2692"/>
+ <source>Left</source>
+ <translation>Вліво</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="471"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="510"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1044"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1083"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2118"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2702"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2741"/>
+ <source>Right</source>
+ <translation>Вправо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="553"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="592"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1126"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1165"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2784"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2823"/>
+ <source>Down</source>
+ <translation>Вниз</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="623"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="662"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2854"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2893"/>
+ <source>Pressed</source>
+ <translation>Натиснення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="672"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="711"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2903"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2942"/>
+ <source>Modifier</source>
+ <translation>Модифікатор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="721"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2952"/>
+ <source>Range</source>
+ <translation>Діапазон</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="754"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2985"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="797"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="3025"/>
+ <source>Deadzone: 0%</source>
+ <translation>Мертва зона: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="821"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="3049"/>
+ <source>Modifier Range: 0%</source>
+ <translation>Діапазон модифікатора: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="867"/>
+ <source>D-Pad</source>
+ <translation>Кнопки напрямків</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1251"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1290"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1296"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1306"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1345"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1288"/>
+ <source>ZL</source>
+ <translation>ZL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1426"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1465"/>
+ <source>Minus</source>
+ <translation>Мінус</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1475"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1514"/>
+ <source>Capture</source>
+ <translation>Захоплення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1545"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1584"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1287"/>
+ <source>Plus</source>
+ <translation>Плюс</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1594"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1633"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1698"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1737"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1290"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1297"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1753"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1792"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1289"/>
+ <source>ZR</source>
+ <translation>ZR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1873"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1912"/>
+ <source>SL</source>
+ <translation>SL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1922"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="1961"/>
+ <source>SR</source>
+ <translation>SR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2030"/>
+ <source>Motion 1</source>
+ <translation>Рух 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2079"/>
+ <source>Motion 2</source>
+ <translation>Рух 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2170"/>
+ <source>Face Buttons</source>
+ <translation>Кнопки A/B/X/Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2228"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2267"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2298"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2337"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2347"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2386"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2429"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2468"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.ui" line="2516"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1292"/>
+ <source>Right Stick</source>
+ <translation>Правий міні-джойстик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="361"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="434"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="529"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="624"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="363"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="436"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="533"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="552"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="626"/>
+ <source>[not set]</source>
+ <translation>[не задано]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="366"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="638"/>
+ <source>Invert button</source>
+ <translation>Інвертувати кнопку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="372"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="629"/>
+ <source>Toggle button</source>
+ <translation>Переключити кнопку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="380"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="580"/>
+ <source>Invert axis</source>
+ <translation>Інвертувати осі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="386"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="390"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="443"/>
+ <source>Set threshold</source>
+ <translation>Встановити поріг</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="390"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="443"/>
+ <source>Choose a value between 0% and 100%</source>
+ <translation>Оберіть значення між 0% і 100%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="402"/>
+ <source>Toggle axis</source>
+ <translation>Переключити осі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="439"/>
+ <source>Set gyro threshold</source>
+ <translation>Встановити поріг гіроскопа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="485"/>
+ <source>Map Analog Stick</source>
+ <translation>Задати аналоговий міні-джойстик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="486"/>
+ <source>After pressing OK, first move your joystick horizontally, and then vertically.
+To invert the axes, first move your joystick vertically, and then horizontally.</source>
+ <translation>Після натискання на ОК, рухайте ваш міні-джойстик горизонтально, а потім вертикально.
+Щоб інвертувати осі, спочатку рухайте ваш міні-джойстик вертикально, а потім горизонтально.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="554"/>
+ <source>Center axis</source>
+ <translation>Центрувати осі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="662"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1016"/>
+ <source>Deadzone: %1%</source>
+ <translation>Мертва зона: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="671"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1021"/>
+ <source>Modifier Range: %1%</source>
+ <translation>Діапазон модифікатора: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="697"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1046"/>
+ <source>Pro Controller</source>
+ <translation>Контролер Pro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1050"/>
+ <source>Dual Joycons</source>
+ <translation>Подвійні Joy-Con&apos;и</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1054"/>
+ <source>Left Joycon</source>
+ <translation>Лівий Joy-Con</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1058"/>
+ <source>Right Joycon</source>
+ <translation>Правий Joy-Con</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1062"/>
+ <source>Handheld</source>
+ <translation>Портативний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1066"/>
+ <source>GameCube Controller</source>
+ <translation>Контролер GameCube</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1075"/>
+ <source>Poke Ball Plus</source>
+ <translation>Poke Ball Plus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1079"/>
+ <source>NES Controller</source>
+ <translation>Контролер NES</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1083"/>
+ <source>SNES Controller</source>
+ <translation>Контролер SNES</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1087"/>
+ <source>N64 Controller</source>
+ <translation>Контролер N64</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1091"/>
+ <source>Sega Genesis</source>
+ <translation>Sega Genesis</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1295"/>
+ <source>Start / Pause</source>
+ <translation>Старт / Пауза</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1298"/>
+ <source>Z</source>
+ <translation>Z</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1299"/>
+ <source>Control Stick</source>
+ <translation>Міні-джойстик керування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1300"/>
+ <source>C-Stick</source>
+ <translation>C-Джойстик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1401"/>
+ <source>Shake!</source>
+ <translation>Потрусіть!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1403"/>
+ <source>[waiting]</source>
+ <translation>[очікування]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1485"/>
+ <source>New Profile</source>
+ <translation>Новий профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1485"/>
+ <source>Enter a profile name:</source>
+ <translation>Введіть ім&apos;я профілю:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1493"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1501"/>
+ <source>Create Input Profile</source>
+ <translation>Створити профіль контролю</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1494"/>
+ <source>The given profile name is not valid!</source>
+ <translation>Задане ім&apos;я профілю недійсне!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1502"/>
+ <source>Failed to create the input profile &quot;%1&quot;</source>
+ <translation>Не вдалося створити профіль контролю &quot;%1&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1522"/>
+ <source>Delete Input Profile</source>
+ <translation>Видалити профіль контролю</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1523"/>
+ <source>Failed to delete the input profile &quot;%1&quot;</source>
+ <translation>Не вдалося видалити профіль контролю &quot;%1&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1545"/>
+ <source>Load Input Profile</source>
+ <translation>Завантажити профіль контролю</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1546"/>
+ <source>Failed to load the input profile &quot;%1&quot;</source>
+ <translation>Не вдалося завантажити профіль контролю &quot;%1&quot;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1565"/>
+ <source>Save Input Profile</source>
+ <translation>Зберегти профіль контролю</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="1566"/>
+ <source>Failed to save the input profile &quot;%1&quot;</source>
+ <translation>Не вдалося зберегти профіль контролю &quot;%1&quot;</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureInputProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="14"/>
+ <source>Create Input Profile</source>
+ <translation>Створити профіль контролю</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="40"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_profile_dialog.ui" line="47"/>
+ <source>Defaults</source>
+ <translation>За замовчуванням</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureMotionTouch</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="6"/>
+ <source>Configure Motion / Touch</source>
+ <translation>Налаштування руху та сенсора</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="15"/>
+ <source>Touch</source>
+ <translation>Сенсор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="23"/>
+ <source>UDP Calibration:</source>
+ <translation>Калібрація UDP:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="30"/>
+ <source>(100, 50) - (1800, 850)</source>
+ <translation>(100, 50) - (1800, 850)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="46"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="73"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="243"/>
+ <source>Configure</source>
+ <translation>Налаштувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="57"/>
+ <source>Touch from button profile:</source>
+ <translation>Торкніться з профілю кнопки:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="85"/>
+ <source>CemuhookUDP Config</source>
+ <translation>Налаштування CemuhookUDP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="91"/>
+ <source>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</source>
+ <translation>Ви можете використовувати будь-яке сумісне з Cemuhook джерело UDP сигналу для руху і сенсора.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="134"/>
+ <source>Server:</source>
+ <translation>Сервер:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="161"/>
+ <source>Port:</source>
+ <translation>Порт:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="188"/>
+ <source>Learn More</source>
+ <translation>Дізнатися більше</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="201"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="266"/>
+ <source>Test</source>
+ <translation>Тест</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="214"/>
+ <source>Add Server</source>
+ <translation>Додати сервер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.ui" line="247"/>
+ <source>Remove Server</source>
+ <translation>Видалити сервер</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="87"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn More&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/using-a-controller-or-android-phone-for-motion-or-touch-input&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Дізнатися більше&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="169"/>
+ <source>%1:%2</source>
+ <translation>%1:%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="174"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="178"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="182"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="188"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="194"/>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="288"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="174"/>
+ <source>Port number has invalid characters</source>
+ <translation>Номер порту містить неприпустимі символи</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="178"/>
+ <source>Port has to be in range 0 and 65353</source>
+ <translation>Порт повинен бути в районі від 0 до 65353</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="182"/>
+ <source>IP address is not valid</source>
+ <translation>IP-адреса недійсна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="188"/>
+ <source>This UDP server already exists</source>
+ <translation>Цей UDP сервер уже існує</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="194"/>
+ <source>Unable to add more than 8 servers</source>
+ <translation>Неможливо додати більше 8 серверів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="210"/>
+ <source>Testing</source>
+ <translation>Тестування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="226"/>
+ <source>Configuring</source>
+ <translation>Налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="257"/>
+ <source>Test Successful</source>
+ <translation>Тест успішний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="258"/>
+ <source>Successfully received data from the server.</source>
+ <translation>Успішно отримано інформацію із сервера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="260"/>
+ <source>Test Failed</source>
+ <translation>Тест провалено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="261"/>
+ <source>Could not receive valid data from the server.&lt;br&gt;Please verify that the server is set up correctly and the address and port are correct.</source>
+ <translation>Не вдалося отримати дійсні дані з сервера.&lt;br&gt;Переконайтеся, що сервер правильно налаштований, а також перевірте адресу та порт.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_motion_touch.cpp" line="289"/>
+ <source>UDP Test or calibration configuration is in progress.&lt;br&gt;Please wait for them to finish.</source>
+ <translation>Тест UDP або калібрація в процесі.&lt;br&gt;Будь ласка, зачекайте завершення.</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureNetwork</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_network.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_network.ui" line="17"/>
+ <source>Network</source>
+ <translation>Мережа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_network.ui" line="25"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_network.ui" line="34"/>
+ <source>Network Interface</source>
+ <translation>Інтерфейс мережі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_network.cpp" line="15"/>
+ <source>None</source>
+ <translation>Нічого</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGame</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="12"/>
+ <source>Dialog</source>
+ <translation>Діалог</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="26"/>
+ <source>Info</source>
+ <translation>Інформація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="85"/>
+ <source>Name</source>
+ <translation>Назва</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="92"/>
+ <source>Title ID</source>
+ <translation>Ідентифікатор гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="129"/>
+ <source>Filename</source>
+ <translation>Ім&apos;я файлу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="156"/>
+ <source>Format</source>
+ <translation>Формат</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="163"/>
+ <source>Version</source>
+ <translation>Версія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="170"/>
+ <source>Size</source>
+ <translation>Розмір</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.ui" line="177"/>
+ <source>Developer</source>
+ <translation>Розробник</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="57"/>
+ <source>Add-Ons</source>
+ <translation>Доповнення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="58"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="59"/>
+ <source>System</source>
+ <translation>Система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="60"/>
+ <source>CPU</source>
+ <translation>ЦП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="61"/>
+ <source>Graphics</source>
+ <translation>Графіка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="62"/>
+ <source>Adv. Graphics</source>
+ <translation>Розш. Графіка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="63"/>
+ <source>Audio</source>
+ <translation>Аудіо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game.cpp" line="66"/>
+ <source>Properties</source>
+ <translation>Властивості</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configuration_shared.cpp" line="91"/>
+ <source>Use global configuration (%1)</source>
+ <translation>Використовувати глобальне налаштування (%1)</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigurePerGameAddons</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.ui" line="17"/>
+ <source>Add-Ons</source>
+ <translation>Доповнення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="46"/>
+ <source>Patch Name</source>
+ <translation>Назва патчу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_per_game_addons.cpp" line="47"/>
+ <source>Version</source>
+ <translation>Версія</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureProfileManager</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="17"/>
+ <source>Profiles</source>
+ <translation>Профілі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="25"/>
+ <source>Profile Manager</source>
+ <translation>Керування профілями</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="42"/>
+ <source>Current User</source>
+ <translation>Поточний користувач</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="80"/>
+ <source>Username</source>
+ <translation>Ім&apos;я користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="110"/>
+ <source>Set Image</source>
+ <translation>Обрати зображення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="130"/>
+ <source>Add</source>
+ <translation>Додати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="140"/>
+ <source>Rename</source>
+ <translation>Перейменувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="150"/>
+ <source>Remove</source>
+ <translation>Видалити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.ui" line="162"/>
+ <source>Profile management is available only when game is not running.</source>
+ <translation>Керування профілями недоступне, поки запущена гра.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="52"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="70"/>
+ <source>Enter Username</source>
+ <translation>Введіть ім&apos;я користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="133"/>
+ <source>Users</source>
+ <translation>Користувачі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="195"/>
+ <source>Enter a username for the new user:</source>
+ <translation>Введіть ім&apos;я користувача для нового профілю:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="215"/>
+ <source>Enter a new username:</source>
+ <translation>Введіть нове ім&apos;я користувача:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="240"/>
+ <source>Confirm Delete</source>
+ <translation>Підтвердити видалення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="241"/>
+ <source>You are about to delete user with name &quot;%1&quot;. Are you sure?</source>
+ <translation>Ви збираєтеся видалити користувача &quot;%1&quot;. Ви впевнені?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="268"/>
+ <source>Select User Image</source>
+ <translation>Оберіть зображення користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="269"/>
+ <source>JPEG Images (*.jpg *.jpeg)</source>
+ <translation>Зображення JPEG (*.jpg *.jpeg)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="278"/>
+ <source>Error deleting image</source>
+ <translation>Помилка під час видалення зображення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="279"/>
+ <source>Error occurred attempting to overwrite previous image at: %1.</source>
+ <translation>Помилка під час спроби перезапису попереднього зображення в: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="287"/>
+ <source>Error deleting file</source>
+ <translation>Помилка під час видалення файлу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="288"/>
+ <source>Unable to delete existing file: %1.</source>
+ <translation>Не вдалося видалити наявний файл: %1.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="295"/>
+ <source>Error creating user image directory</source>
+ <translation>Помилка під час створення папки користувацьких зображень</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="296"/>
+ <source>Unable to create directory %1 for storing user images.</source>
+ <translation>Не вийшло створити папку %1 для зберігання зображень користувача.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="301"/>
+ <source>Error copying user image</source>
+ <translation>Помилка під час копіювання зображення користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="302"/>
+ <source>Unable to copy image from %1 to %2</source>
+ <translation>Не вийшло скопіювати зображення з %1 у %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="311"/>
+ <source>Error resizing user image</source>
+ <translation>Помилка під час зміни розміру зображення користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_profile_manager.cpp" line="312"/>
+ <source>Unable to resize image</source>
+ <translation>Неможливо змінити розмір зображення</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureRingController</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="14"/>
+ <source>Configure Ring Controller</source>
+ <translation>Налаштування контролера Ring</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="26"/>
+ <source>If you want to use this controller configure player 1 as right controller and player 2 as dual joycon before starting the game to allow this controller to be detected properly.</source>
+ <translation>Якщо ви хочете використовувати цей контролер, налаштуйте гравця 1 як правий контролер, а гравця 2 як подвійний Joy-Соп перед початком гри, щоб цей контролер був виявлений правильно.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="52"/>
+ <source>Ring Sensor Parameters</source>
+ <translation>Параметри сенсора Ring</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="84"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="123"/>
+ <source>Pull</source>
+ <translation>Потягнути</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="133"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="172"/>
+ <source>Push</source>
+ <translation>Натиснути</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="206"/>
+ <source>Deadzone: 0%</source>
+ <translation>Мертва зона: 0%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.ui" line="248"/>
+ <source>Restore Defaults</source>
+ <translation>За замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="159"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="161"/>
+ <source>[not set]</source>
+ <translation>[не задано]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="163"/>
+ <source>Invert axis</source>
+ <translation>Інвертувати осі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="182"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="238"/>
+ <source>Deadzone: %1%</source>
+ <translation>Мертва зона: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="262"/>
+ <source>[waiting]</source>
+ <translation>[очікування]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureSystem</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="17"/>
+ <source>System</source>
+ <translation>Система</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="25"/>
+ <source>System Settings</source>
+ <translation>Налаштування системи</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="33"/>
+ <source>Region:</source>
+ <translation>Регіон:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="41"/>
+ <source>Auto</source>
+ <translation>Авто</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="46"/>
+ <source>Default</source>
+ <translation>За замовчуванням</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="51"/>
+ <source>CET</source>
+ <translation>CET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="56"/>
+ <source>CST6CDT</source>
+ <translation>CST6CDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="61"/>
+ <source>Cuba</source>
+ <translation>Куба</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="66"/>
+ <source>EET</source>
+ <translation>EET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="71"/>
+ <source>Egypt</source>
+ <translation>Єгипет</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="76"/>
+ <source>Eire</source>
+ <translation>Ейре</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="81"/>
+ <source>EST</source>
+ <translation>EST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="86"/>
+ <source>EST5EDT</source>
+ <translation>EST5EDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="91"/>
+ <source>GB</source>
+ <translation>GB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="96"/>
+ <source>GB-Eire</source>
+ <translation>GB-Ейре</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="101"/>
+ <source>GMT</source>
+ <translation>GMT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="106"/>
+ <source>GMT+0</source>
+ <translation>GMT+0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="111"/>
+ <source>GMT-0</source>
+ <translation>GMT-0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="116"/>
+ <source>GMT0</source>
+ <translation>GMT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="121"/>
+ <source>Greenwich</source>
+ <translation>Гринвіч</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="126"/>
+ <source>Hongkong</source>
+ <translation>Гонконг</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="131"/>
+ <source>HST</source>
+ <translation>HST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="136"/>
+ <source>Iceland</source>
+ <translation>Ісландія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="141"/>
+ <source>Iran</source>
+ <translation>Іран</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="146"/>
+ <source>Israel</source>
+ <translation>Ізраїль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="151"/>
+ <source>Jamaica</source>
+ <translation>Ямайка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="156"/>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="275"/>
+ <source>Japan</source>
+ <translation>Японія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="161"/>
+ <source>Kwajalein</source>
+ <translation>Кваджалейн</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="166"/>
+ <source>Libya</source>
+ <translation>Лівія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="171"/>
+ <source>MET</source>
+ <translation>MET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="176"/>
+ <source>MST</source>
+ <translation>MST</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="181"/>
+ <source>MST7MDT</source>
+ <translation>MST7MDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="186"/>
+ <source>Navajo</source>
+ <translation>Навахо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="191"/>
+ <source>NZ</source>
+ <translation>NZ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="196"/>
+ <source>NZ-CHAT</source>
+ <translation>NZ-CHAT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="201"/>
+ <source>Poland</source>
+ <translation>Польща</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="206"/>
+ <source>Portugal</source>
+ <translation>Португалія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="211"/>
+ <source>PRC</source>
+ <translation>PRC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="216"/>
+ <source>PST8PDT</source>
+ <translation>PST8PDT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="221"/>
+ <source>ROC</source>
+ <translation>ROC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="226"/>
+ <source>ROK</source>
+ <translation>ROK</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="231"/>
+ <source>Singapore</source>
+ <translation>Сінгапур</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="236"/>
+ <source>Turkey</source>
+ <translation>Туреччина</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="241"/>
+ <source>UCT</source>
+ <translation>UCT</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="246"/>
+ <source>Universal</source>
+ <translation>Універсальний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="251"/>
+ <source>UTC</source>
+ <translation>UTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="256"/>
+ <source>W-SU</source>
+ <translation>W-SU</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="261"/>
+ <source>WET</source>
+ <translation>WET</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="266"/>
+ <source>Zulu</source>
+ <translation>Зулуси</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="280"/>
+ <source>USA</source>
+ <translation>США</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="285"/>
+ <source>Europe</source>
+ <translation>Європа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="290"/>
+ <source>Australia</source>
+ <translation>Австралія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="295"/>
+ <source>China</source>
+ <translation>Китай</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="300"/>
+ <source>Korea</source>
+ <translation>Корея</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="305"/>
+ <source>Taiwan</source>
+ <translation>Тайвань</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="313"/>
+ <source>Time Zone:</source>
+ <translation>Часовий пояс:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="320"/>
+ <source>Note: this can be overridden when region setting is auto-select</source>
+ <translation>Примітка: може бути перезаписано якщо регіон вибирається автоматично</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="324"/>
+ <source>Japanese (日本語)</source>
+ <translation>Японська (日本語)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="329"/>
+ <source>English</source>
+ <translation>Англійська (English)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="334"/>
+ <source>French (français)</source>
+ <translation>Французька (français)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="339"/>
+ <source>German (Deutsch)</source>
+ <translation>Німецька (Deutsch)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="344"/>
+ <source>Italian (italiano)</source>
+ <translation>Італійська (italiano)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="349"/>
+ <source>Spanish (español)</source>
+ <translation>Іспанська (español)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="354"/>
+ <source>Chinese</source>
+ <translation>Китайська</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="359"/>
+ <source>Korean (한국어)</source>
+ <translation>Корейська (한국어)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="364"/>
+ <source>Dutch (Nederlands)</source>
+ <translation>Голландська (Nederlands)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="369"/>
+ <source>Portuguese (português)</source>
+ <translation>Португальська (português)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="374"/>
+ <source>Russian (Русский)</source>
+ <translation>Російська (Русский)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="379"/>
+ <source>Taiwanese</source>
+ <translation>Тайванська</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="384"/>
+ <source>British English</source>
+ <translation>Британська Англійська</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="389"/>
+ <source>Canadian French</source>
+ <translation>Канадська Французька</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="394"/>
+ <source>Latin American Spanish</source>
+ <translation>Латиноамериканська Іспанська</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="399"/>
+ <source>Simplified Chinese</source>
+ <translation>Спрощена Китайська</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="404"/>
+ <source>Traditional Chinese (正體中文)</source>
+ <translation>Традиційна Китайська (正體中文)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="409"/>
+ <source>Brazilian Portuguese (português do Brasil)</source>
+ <translation>Бразильська Португальська (português do Brasil)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="417"/>
+ <source>Custom RTC</source>
+ <translation>Користувацький RTC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="424"/>
+ <source>Language</source>
+ <translation>Мова</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="431"/>
+ <source>RNG Seed</source>
+ <translation>Сід RNG</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="439"/>
+ <source>Mono</source>
+ <translation>Моно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="444"/>
+ <source>Stereo</source>
+ <translation>Стерео</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="449"/>
+ <source>Surround</source>
+ <translation>Об&apos;ємний звук</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="457"/>
+ <source>Console ID:</source>
+ <translation>Ідентифікатор консолі:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="464"/>
+ <source>Sound output mode</source>
+ <translation>Режим виводу звуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="512"/>
+ <source>Regenerate</source>
+ <translation>Перегенерувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.ui" line="537"/>
+ <source>System settings are available only when game is not running.</source>
+ <translation>Налаштування системи доступні тільки тоді, коли гру не запущено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="161"/>
+ <source>This will replace your current virtual Switch with a new one. Your current virtual Switch will not be recoverable. This might have unexpected effects in games. This might fail, if you use an outdated config savegame. Continue?</source>
+ <translation>Це замінить ваш поточний віртуальний Switch новим. Ваш поточний віртуальний Switch буде безповоротно втрачено. Це може мати несподівані наслідки в іграх. Може не спрацювати, якщо ви використовуєте застарілу конфігурацію збережених ігор. Продовжити?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="165"/>
+ <source>Warning</source>
+ <translation>Увага</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_system.cpp" line="173"/>
+ <source>Console ID: 0x%1</source>
+ <translation>Ідентифікатор консолі: 0x%1</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTas</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="11"/>
+ <source>TAS</source>
+ <translation>TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="17"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation, please consult the &lt;a href=&quot;https://yuzu-emu.org/help/feature/tas/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;help page&lt;/span&gt;&lt;/a&gt; on the yuzu website.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Зчитує вхідні дані контролера зі скриптів у тому ж форматі, що і скрипти TAS-nx.&lt;br/&gt;Для більш детального пояснення зверніться до &lt;a href=&quot;https://yuzu-emu.org/help/feature/tas/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;сторінки допомоги&lt;/span&gt;&lt;/a&gt; на сайті yuzu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="27"/>
+ <source>To check which hotkeys control the playback/recording, please refer to the Hotkey settings (Configure -&gt; General -&gt; Hotkeys).</source>
+ <translation>Щоб перевірити, які гарячі клавіші керують відтворенням/записом, зверніться до налаштувань гарячих клавіш (Налаштування - Загальні -&gt; Гарячі клавіші).</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="37"/>
+ <source>WARNING: This is an experimental feature.&lt;br/&gt;It will not play back scripts frame perfectly with the current, imperfect syncing method.</source>
+ <translation>ПОПЕРЕДЖЕННЯ: Це експериментальна функція.&lt;br/&gt;Вона не буде ідеально відтворювати кадри сценаріїв за поточного недосконалого методу синхронізації.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="54"/>
+ <source>Settings</source>
+ <translation>Налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="60"/>
+ <source>Enable TAS features</source>
+ <translation>Увімкнути функції TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="67"/>
+ <source>Loop script</source>
+ <translation>Зациклити скрипт</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="77"/>
+ <source>Pause execution during loads</source>
+ <translation>Призупинити виконання під час завантаження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="91"/>
+ <source>Script Directory</source>
+ <translation>Папка для скриптів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="97"/>
+ <source>Path</source>
+ <translation>Шлях</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.ui" line="104"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTasDialog</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.cpp" line="19"/>
+ <source>TAS Configuration</source>
+ <translation>Налаштування TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_tas.cpp" line="50"/>
+ <source>Select TAS Load Directory...</source>
+ <translation>Обрати папку завантаження TAS...</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchFromButton</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="14"/>
+ <source>Configure Touchscreen Mappings</source>
+ <translation>Налаштування відображення сенсорного екрана</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="22"/>
+ <source>Mapping:</source>
+ <translation>Прив&apos;язки:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="48"/>
+ <source>New</source>
+ <translation>Новий</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="61"/>
+ <source>Delete</source>
+ <translation>Видалити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="74"/>
+ <source>Rename</source>
+ <translation>Перейменувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="92"/>
+ <source>Click the bottom area to add a point, then press a button to bind.
+Drag points to change position, or double-click table cells to edit values.</source>
+ <translation>Натисніть на нижній області, щоб додати точку, після чого натисніть кнопку для прив&apos;язки.
+Перетягніть точки, щоб змінити позицію, або натисніть двічі на комірки таблиці для зміни значень.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.ui" line="116"/>
+ <source>Delete Point</source>
+ <translation>Видалити точку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="78"/>
+ <source>Button</source>
+ <translation>Кнопка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="78"/>
+ <source>X</source>
+ <comment>X axis</comment>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="78"/>
+ <source>Y</source>
+ <comment>Y axis</comment>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="195"/>
+ <source>New Profile</source>
+ <translation>Новий профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="195"/>
+ <source>Enter the name for the new profile.</source>
+ <translation>Введіть ім&apos;я вашого нового профілю.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="206"/>
+ <source>Delete Profile</source>
+ <translation>Видалити профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="206"/>
+ <source>Delete profile %1?</source>
+ <translation>Видалити профіль %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="219"/>
+ <source>Rename Profile</source>
+ <translation>Перейменувати профіль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="219"/>
+ <source>New name:</source>
+ <translation>Нова назва:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="231"/>
+ <source>[press key]</source>
+ <translation>[натисніть клавішу]</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureTouchscreenAdvanced</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="14"/>
+ <source>Configure Touchscreen</source>
+ <translation>Налаштування сенсорного екрана</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="26"/>
+ <source>Warning: The settings in this page affect the inner workings of yuzu&apos;s emulated touchscreen. Changing them may result in undesirable behavior, such as the touchscreen partially or not working. You should only use this page if you know what you are doing.</source>
+ <translation>Увага: Налаштування на цій сторінці впливають на внутрішню роботу емульованого сенсорного екрана yuzu. Їх зміна може призвести до небажаної поведінки, як часткова або повна непрацездатність сенсорного екрана. Використовуйте цю сторінку лише якщо ви знаєте, що робите.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="52"/>
+ <source>Touch Parameters</source>
+ <translation>Параметри сенсора</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="71"/>
+ <source>Touch Diameter Y</source>
+ <translation>Діаметр сенсора Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="91"/>
+ <source>Touch Diameter X</source>
+ <translation>Діаметр сенсора X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="98"/>
+ <source>Rotational Angle</source>
+ <translation>Кут повороту</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touchscreen_advanced.ui" line="132"/>
+ <source>Restore Defaults</source>
+ <translation>За замовчуванням</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUI</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="20"/>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="28"/>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="40"/>
+ <source>None</source>
+ <translation>Нічого</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="21"/>
+ <source>Small (32x32)</source>
+ <translation>Маленький (32х32)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="22"/>
+ <source>Standard (64x64)</source>
+ <translation>Стандартний (64х64)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="23"/>
+ <source>Large (128x128)</source>
+ <translation>Великий (128х128)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="24"/>
+ <source>Full Size (256x256)</source>
+ <translation>Повнорозмірний (256х256)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="29"/>
+ <source>Small (24x24)</source>
+ <translation>Маленький (24х24)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="30"/>
+ <source>Standard (48x48)</source>
+ <translation>Стандартний (48х48)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="31"/>
+ <source>Large (72x72)</source>
+ <translation>Великий (72х72)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="36"/>
+ <source>Filename</source>
+ <translation>Ім&apos;я файлу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="37"/>
+ <source>Filetype</source>
+ <translation>Тип файлу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="38"/>
+ <source>Title ID</source>
+ <translation>Ідентифікатор гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="39"/>
+ <source>Title Name</source>
+ <translation>Назва гри</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureUi</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="17"/>
+ <source>UI</source>
+ <translation>Інтерфейс</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="23"/>
+ <source>General</source>
+ <translation>Загальні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="31"/>
+ <source>Note: Changing language will apply your configuration.</source>
+ <translation>Примітка: Зміна мови призведе до застосування налаштувань.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="43"/>
+ <source>Interface language:</source>
+ <translation>Мова інтерфейсу:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="57"/>
+ <source>Theme:</source>
+ <translation>Тема:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="74"/>
+ <source>Game List</source>
+ <translation>Список ігор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="82"/>
+ <source>Show Compatibility List</source>
+ <translation>Показати список сумісності</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="89"/>
+ <source>Show Add-Ons Column</source>
+ <translation>Показувати стовпець доповнень</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="98"/>
+ <source>Game Icon Size:</source>
+ <translation>Розмір іконки гри:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="112"/>
+ <source>Folder Icon Size:</source>
+ <translation>Розмір іконки папки:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="126"/>
+ <source>Row 1 Text:</source>
+ <translation>Текст 1-го рядку:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="140"/>
+ <source>Row 2 Text:</source>
+ <translation>Текст 2-го рядку:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="157"/>
+ <source>Screenshots</source>
+ <translation>Знімки екрану</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="165"/>
+ <source>Ask Where To Save Screenshots (Windows Only)</source>
+ <translation>Запитувати куди зберігати знімки екрану (Тільки для Windows)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="174"/>
+ <source>Screenshots Path: </source>
+ <translation>Папка для знімків екрану:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.ui" line="184"/>
+ <source>...</source>
+ <translation>...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="94"/>
+ <source>Select Screenshots Path...</source>
+ <translation>Виберіть папку для знімків екрану...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ui.cpp" line="219"/>
+ <source>&lt;System&gt;</source>
+ <translation>&lt;System&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureVibration</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="14"/>
+ <source>Configure Vibration</source>
+ <translation>Налаштування вібрації</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="23"/>
+ <source>Press any controller button to vibrate the controller.</source>
+ <translation>Натисніть будь-яку кнопку контролера, щоб викликати вібрацію.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="30"/>
+ <source>Vibration</source>
+ <translation>Вібрація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="63"/>
+ <source>Player 1</source>
+ <translation>Гравець 1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="96"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="148"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="200"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="252"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="322"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="374"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="426"/>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="478"/>
+ <source>%</source>
+ <translation>%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="115"/>
+ <source>Player 2</source>
+ <translation>Гравець 2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="167"/>
+ <source>Player 3</source>
+ <translation>Гравець 3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="219"/>
+ <source>Player 4</source>
+ <translation>Гравець 4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="289"/>
+ <source>Player 5</source>
+ <translation>Гравець 5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="341"/>
+ <source>Player 6</source>
+ <translation>Гравець 6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="393"/>
+ <source>Player 7</source>
+ <translation>Гравець 7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="445"/>
+ <source>Player 8</source>
+ <translation>Гравець 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="503"/>
+ <source>Settings</source>
+ <translation>Налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_vibration.ui" line="509"/>
+ <source>Enable Accurate Vibration</source>
+ <translation>Увімкнути точну вібрацію</translation>
+ </message>
+</context>
+<context>
+ <name>ConfigureWeb</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="14"/>
+ <source>Form</source>
+ <translation>Форма</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="17"/>
+ <source>Web</source>
+ <translation>Мережа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="25"/>
+ <source>yuzu Web Service</source>
+ <translation>Веб-сервіс yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="31"/>
+ <source>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</source>
+ <translation>Надаючи своє ім&apos;я користувача і токен, ви погоджуєтеся дозволити yuzu збирати додаткові дані про використання, які можуть включати інформацію, що ідентифікує користувача.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="159"/>
+ <source>Verify</source>
+ <translation>Підтвердити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="56"/>
+ <source>Sign up</source>
+ <translation>Реєстрація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="66"/>
+ <source>Token: </source>
+ <translation>Токен:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="76"/>
+ <source>Username: </source>
+ <translation>Ім&apos;я користувача:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="93"/>
+ <source>What is my token?</source>
+ <translation>Що таке токен і де його знайти?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="118"/>
+ <source>Web Service configuration can only be changed when a public room isn&apos;t being hosted.</source>
+ <translation>Налаштування веб-служби можуть бути змінені тільки в тому випадку, коли не хоститься публічна кімната.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="128"/>
+ <source>Telemetry</source>
+ <translation>Телеметрія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="134"/>
+ <source>Share anonymous usage data with the yuzu team</source>
+ <translation>Ділитися анонімною інформацією про використання з командою yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="141"/>
+ <source>Learn more</source>
+ <translation>Дізнатися більше</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="150"/>
+ <source>Telemetry ID:</source>
+ <translation>Ідентифікатор телеметрії:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="166"/>
+ <source>Regenerate</source>
+ <translation>Перегенерувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="180"/>
+ <source>Discord Presence</source>
+ <translation>Discord Presence</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.ui" line="186"/>
+ <source>Show Current Game in your Discord Status</source>
+ <translation>Показувати поточну гру у вашому статусі Discord</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="68"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Learn more&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Дізнатися більше&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="72"/>
+ <source>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Sign up&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://profile.yuzu-emu.org/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Реєстрація&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="76"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;What is my token?&lt;/span&gt;&lt;/a&gt;</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/wiki/yuzu-web-service/&apos;&gt;&lt;span style=&quot;text-decoration: underline; color:#039be5;&quot;&gt;Що таке токен і де його знайти?&lt;/span&gt;&lt;/a&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="80"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="125"/>
+ <source>Telemetry ID: 0x%1</source>
+ <translation>Ідентифікатор телеметрії: 0x%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="170"/>
+ <source>Unspecified</source>
+ <translation>Відсутній</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="117"/>
+ <source>Token not verified</source>
+ <translation>Токен не підтверджено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="118"/>
+ <source>Token was not verified. The change to your token has not been saved.</source>
+ <translation>Токен не було підтверджено. Зміну вашого токена не було збережено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="141"/>
+ <source>Unverified, please click Verify before saving configuration</source>
+ <comment>Tooltip</comment>
+ <translation>Не підтверджено, будь ласка, натисніть кнопку Підтвердити, перш ніж зберігати конфігурацію.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="147"/>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="149"/>
+ <source>Verifying...</source>
+ <translation>Підтверждення...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="164"/>
+ <source>Verified</source>
+ <comment>Tooltip</comment>
+ <translation>Підтверджено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="169"/>
+ <source>Verification failed</source>
+ <comment>Tooltip</comment>
+ <translation>Підтверждення не було успішним</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="171"/>
+ <source>Verification failed</source>
+ <translation>Підтверждення не було успішним</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_web.cpp" line="172"/>
+ <source>Verification failed. Check that you have entered your token correctly, and that your internet connection is working.</source>
+ <translation>Підтверждення не було успішним. Переконайтеся, що ви правильно ввели свій токен і що ваше інтернет-з&apos;єднання працює.</translation>
+ </message>
+</context>
+<context>
+ <name>ControllerDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/controller.cpp" line="20"/>
+ <source>Controller P1</source>
+ <translation>Контролер P1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/controller.cpp" line="59"/>
+ <source>&amp;Controller P1</source>
+ <translation>[&amp;C] Контролер P1</translation>
+ </message>
+</context>
+<context>
+ <name>DirectConnect</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="14"/>
+ <source>Direct Connect</source>
+ <translation>Пряме підключення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="33"/>
+ <source>IP Address</source>
+ <translation>IP-адреса</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="56"/>
+ <source>IP</source>
+ <translation>IP</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="63"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;IPv4 address of the host&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;IPv4 адреса хоста&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="73"/>
+ <source>Port</source>
+ <translation>Порт</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="80"/>
+ <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Port number the host is listening on&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Номер порту, який прослуховується хостом&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="100"/>
+ <source>Nickname</source>
+ <translation>Псевдонім</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="114"/>
+ <source>Password</source>
+ <translation>Пароль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.ui" line="156"/>
+ <source>Connect</source>
+ <translation>Підключитися</translation>
+ </message>
+</context>
+<context>
+ <name>DirectConnectWindow</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.cpp" line="127"/>
+ <source>Connecting</source>
+ <translation>Підключення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/direct_connect.cpp" line="132"/>
+ <source>Connect</source>
+ <translation>Підключитися</translation>
+ </message>
+</context>
+<context>
+ <name>GMainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="188"/>
+ <source>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Anonymous data is collected&lt;/a&gt; to help improve yuzu. &lt;br/&gt;&lt;br/&gt;Would you like to share your usage data with us?</source>
+ <translation>&lt;a href=&apos;https://yuzu-emu.org/help/feature/telemetry/&apos;&gt;Анонімні дані збираються для того,&lt;/a&gt; щоб допомогти поліпшити роботу yuzu. &lt;br/&gt;&lt;br/&gt;Хотіли б ви ділитися даними про використання з нами?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="191"/>
+ <source>Telemetry</source>
+ <translation>Телеметрія</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="405"/>
+ <source>Broken Vulkan Installation Detected</source>
+ <translation>Виявлено пошкоджену інсталяцію Vulkan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="406"/>
+ <source>Vulkan initialization failed during boot.&lt;br&gt;&lt;br&gt;Click &lt;a href=&apos;https://yuzu-emu.org/wiki/faq/#yuzu-starts-with-the-error-broken-vulkan-installation-detected&apos;&gt;here for instructions to fix the issue&lt;/a&gt;.</source>
+ <translation>Не вдалося виконати ініціалізацію Vulkan під час завантаження.&lt;br&gt;&lt;br&gt;Натисніть &lt;a href=&apos;https://yuzu-emu.org/wiki/faq/#yuzu-starts-with-the-error-broken-vulkan-installation-detected&apos;&gt;тут для отримання інструкцій щодо усунення проблеми&lt;/a&gt;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="734"/>
+ <source>Loading Web Applet...</source>
+ <translation>Завантаження веб-аплета...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="781"/>
+ <location filename="../../src/yuzu/main.cpp" line="784"/>
+ <source>Disable Web Applet</source>
+ <translation>Вимкнути веб-аплет</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="785"/>
+ <source>Disabling the web applet can lead to undefined behavior and should only be used with Super Mario 3D All-Stars. Are you sure you want to disable the web applet?
+(This can be re-enabled in the Debug settings.)</source>
+ <translation>Вимкнення веб-апплета може призвести до несподіваної поведінки, і його слід вимикати лише заради Super Mario 3D All-Stars. Ви впевнені, що хочете вимкнути веб-апплет?
+(Його можна знову ввімкнути в налаштуваннях налагодження.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="892"/>
+ <source>The amount of shaders currently being built</source>
+ <translation>Кількість створюваних шейдерів на цей момент</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="894"/>
+ <source>The current selected resolution scaling multiplier.</source>
+ <translation>Поточний обраний множник масштабування роздільної здатності.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="897"/>
+ <source>Current emulation speed. Values higher or lower than 100% indicate emulation is running faster or slower than a Switch.</source>
+ <translation>Поточна швидкість емуляції. Значення вище або нижче 100% вказують на те, що емуляція йде швидше або повільніше, ніж на Switch.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="900"/>
+ <source>How many frames per second the game is currently displaying. This will vary from game to game and scene to scene.</source>
+ <translation>Кількість кадрів на секунду в цей момент. Значення буде змінюватися між іграми та сценами.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="904"/>
+ <source>Time taken to emulate a Switch frame, not counting framelimiting or v-sync. For full-speed emulation this should be at most 16.67 ms.</source>
+ <translation>Час, який потрібен для емуляції 1 кадру Switch, не беручи до уваги обмеження FPS або вертикальну синхронізацію. Для емуляції в повній швидкості значення має бути не більше 16,67 мс.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="983"/>
+ <source>VULKAN</source>
+ <translation>VULKAN</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="983"/>
+ <source>OPENGL</source>
+ <translation>OPENGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1045"/>
+ <source>&amp;Clear Recent Files</source>
+ <translation>[&amp;C] Очистити нещодавні файли</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1353"/>
+ <source>&amp;Continue</source>
+ <translation>[&amp;C] Продовжити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1355"/>
+ <source>&amp;Pause</source>
+ <translation>[&amp;P] Пауза</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1435"/>
+ <source>yuzu is running a game</source>
+ <extracomment>TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the computer from sleeping</extracomment>
+ <translation>В yuzu запущено гру</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1566"/>
+ <source>Warning Outdated Game Format</source>
+ <translation>Попередження застарілий формат гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1567"/>
+ <source>You are using the deconstructed ROM directory format for this game, which is an outdated format that has been superseded by others such as NCA, NAX, XCI, or NSP. Deconstructed ROM directories lack icons, metadata, and update support.&lt;br&gt;&lt;br&gt;For an explanation of the various Switch formats yuzu supports, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;check out our wiki&lt;/a&gt;. This message will not be shown again.</source>
+ <translation>Для цієї гри ви використовуєте розархівований формат ROM&apos;а, який є застарілим і був замінений іншими, такими як NCA, NAX, XCI або NSP. У розархівованих каталогах ROM&apos;а відсутні іконки, метадані та підтримка оновлень. &lt;br&gt;&lt;br&gt;Для отримання інформації про різні формати Switch, підтримувані yuzu, &lt;a href=&apos;https://yuzu-emu.org/wiki/overview-of-switch-game-formats&apos;&gt;перегляньте нашу вікі&lt;/a&gt;. Це повідомлення більше не буде відображатися.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1579"/>
+ <location filename="../../src/yuzu/main.cpp" line="1613"/>
+ <source>Error while loading ROM!</source>
+ <translation>Помилка під час завантаження ROM!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1580"/>
+ <source>The ROM format is not supported.</source>
+ <translation>Формат ROM&apos;а не підтримується.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1584"/>
+ <source>An error occurred initializing the video core.</source>
+ <translation>Сталася помилка під час ініціалізації відеоядра.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1585"/>
+ <source>yuzu has encountered an error while running the video core. This is usually caused by outdated GPU drivers, including integrated ones. Please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://yuzu-emu.org/help/reference/log-files/&apos;&gt;How to Upload the Log File&lt;/a&gt;. </source>
+ <translation>yuzu зіткнувся з помилкою під час запуску відеоядра. Зазвичай це спричинено застарілими драйверами ГП, включно з інтегрованими. Перевірте журнал для отримання більш детальної інформації. Додаткову інформацію про доступ до журналу дивіться на наступній сторінці: &lt;a href=&apos;https://yuzu-emu.org/help/reference/log-files/&apos;&gt;Як завантажити файл журналу&lt;/a&gt;. </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1600"/>
+ <source>Error while loading ROM! %1</source>
+ <comment>%1 signifies a numeric error code.</comment>
+ <translation>Помилка під час завантаження ROM&apos;а! %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1603"/>
+ <source>%1&lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to redump your files.&lt;br&gt;You can refer to the yuzu wiki&lt;/a&gt; or the yuzu Discord&lt;/a&gt; for help.</source>
+ <comment>%1 signifies an error string.</comment>
+ <translation>%1&lt;br&gt;Будь ласка, дотримуйтесь &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;короткого керівництва користувача yuzu&lt;/a&gt; щоб пере-дампити ваші файли&lt;br&gt;Ви можете звернутися до вікі yuzu&lt;/a&gt; або Discord yuzu&lt;/a&gt; для допомоги</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1614"/>
+ <source>An unknown error occurred. Please see the log for more details.</source>
+ <translation>Сталася невідома помилка. Будь ласка, перевірте журнал для подробиць.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1746"/>
+ <source>(64-bit)</source>
+ <translation>(64-бітний)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1746"/>
+ <source>(32-bit)</source>
+ <translation>(32-бітний)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1747"/>
+ <source>%1 %2</source>
+ <comment>%1 is the title name. %2 indicates if the title is 64-bit or 32-bit</comment>
+ <translation>%1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1897"/>
+ <source>Save Data</source>
+ <translation>Збереження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1947"/>
+ <source>Mod Data</source>
+ <translation>Дані модів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1959"/>
+ <source>Error Opening %1 Folder</source>
+ <translation>Помилка під час відкриття папки %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1960"/>
+ <location filename="../../src/yuzu/main.cpp" line="2366"/>
+ <source>Folder does not exist!</source>
+ <translation>Папка не існує!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1972"/>
+ <source>Error Opening Transferable Shader Cache</source>
+ <translation>Помилка під час відкриття переносного кешу шейдерів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="1973"/>
+ <source>Failed to create the shader cache directory for this title.</source>
+ <translation>Не вдалося створити папку кешу шейдерів для цієї гри.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2025"/>
+ <source>Contents</source>
+ <translation>Зміст</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2027"/>
+ <source>Update</source>
+ <translation>Оновлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2029"/>
+ <source>DLC</source>
+ <translation>DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2036"/>
+ <source>Remove Entry</source>
+ <translation>Видалити запис</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2036"/>
+ <source>Remove Installed Game %1?</source>
+ <translation>Видалити встановлену гру %1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2066"/>
+ <location filename="../../src/yuzu/main.cpp" line="2082"/>
+ <location filename="../../src/yuzu/main.cpp" line="2113"/>
+ <location filename="../../src/yuzu/main.cpp" line="2174"/>
+ <location filename="../../src/yuzu/main.cpp" line="2192"/>
+ <location filename="../../src/yuzu/main.cpp" line="2215"/>
+ <source>Successfully Removed</source>
+ <translation>Успішно видалено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2067"/>
+ <source>Successfully removed the installed base game.</source>
+ <translation>Встановлену гру успішно видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2070"/>
+ <location filename="../../src/yuzu/main.cpp" line="2085"/>
+ <location filename="../../src/yuzu/main.cpp" line="2108"/>
+ <source>Error Removing %1</source>
+ <translation>Помилка під час видалення %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2071"/>
+ <source>The base game is not installed in the NAND and cannot be removed.</source>
+ <translation>Гру не встановлено в NAND і не може буде видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2083"/>
+ <source>Successfully removed the installed update.</source>
+ <translation>Встановлене оновлення успішно видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2086"/>
+ <source>There is no update installed for this title.</source>
+ <translation>Для цієї гри не було встановлено оновлення.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2109"/>
+ <source>There are no DLC installed for this title.</source>
+ <translation>Для цієї гри не було встановлено DLC.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2114"/>
+ <source>Successfully removed %1 installed DLC.</source>
+ <translation>Встановлений DLC %1 було успішно видалено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2122"/>
+ <source>Delete OpenGL Transferable Shader Cache?</source>
+ <translation>Видалити переносний кеш шейдерів OpenGL?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2124"/>
+ <source>Delete Vulkan Transferable Shader Cache?</source>
+ <translation>Видалити переносний кеш шейдерів Vulakn?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2126"/>
+ <source>Delete All Transferable Shader Caches?</source>
+ <translation>Видалити весь переносний кеш шейдерів?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2128"/>
+ <source>Remove Custom Game Configuration?</source>
+ <translation>Видалити користувацьке налаштування гри?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2134"/>
+ <source>Remove File</source>
+ <translation>Видалити файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2169"/>
+ <location filename="../../src/yuzu/main.cpp" line="2177"/>
+ <source>Error Removing Transferable Shader Cache</source>
+ <translation>Помилка під час видалення переносного кешу шейдерів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2170"/>
+ <location filename="../../src/yuzu/main.cpp" line="2188"/>
+ <source>A shader cache for this title does not exist.</source>
+ <translation>Кеш шейдерів для цієї гри не існує.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2175"/>
+ <source>Successfully removed the transferable shader cache.</source>
+ <translation>Переносний кеш шейдерів успішно видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2178"/>
+ <source>Failed to remove the transferable shader cache.</source>
+ <translation>Не вдалося видалити переносний кеш шейдерів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2187"/>
+ <location filename="../../src/yuzu/main.cpp" line="2195"/>
+ <source>Error Removing Transferable Shader Caches</source>
+ <translation>Помилка під час видалення переносного кешу шейдерів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2193"/>
+ <source>Successfully removed the transferable shader caches.</source>
+ <translation>Переносний кеш шейдерів успішно видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2196"/>
+ <source>Failed to remove the transferable shader cache directory.</source>
+ <translation>Помилка під час видалення папки переносного кешу шейдерів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2209"/>
+ <location filename="../../src/yuzu/main.cpp" line="2218"/>
+ <source>Error Removing Custom Configuration</source>
+ <translation>Помилка під час видалення користувацького налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2210"/>
+ <source>A custom configuration for this title does not exist.</source>
+ <translation>Користувацьких налаштувань для цієї гри не існує.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2216"/>
+ <source>Successfully removed the custom game configuration.</source>
+ <translation>Користувацьке налаштування гри успішно видалено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2219"/>
+ <source>Failed to remove the custom game configuration.</source>
+ <translation>Не вдалося видалити користувацьке налаштування гри.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2226"/>
+ <location filename="../../src/yuzu/main.cpp" line="2305"/>
+ <source>RomFS Extraction Failed!</source>
+ <translation>Не вдалося вилучити RomFS!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2227"/>
+ <source>There was an error copying the RomFS files or the user cancelled the operation.</source>
+ <translation>Сталася помилка під час копіювання файлів RomFS або користувач скасував операцію.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2285"/>
+ <source>Full</source>
+ <translation>Повний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2285"/>
+ <source>Skeleton</source>
+ <translation>Скелет</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2287"/>
+ <source>Select RomFS Dump Mode</source>
+ <translation>Виберіть режим дампа RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2288"/>
+ <source>Please select the how you would like the RomFS dumped.&lt;br&gt;Full will copy all of the files into the new directory while &lt;br&gt;skeleton will only create the directory structure.</source>
+ <translation>Будь ласка, виберіть, як ви хочете виконати дамп RomFS &lt;br&gt;Повний скопіює всі файли в нову папку, тоді як &lt;br&gt;скелет створить лише структуру папок.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2306"/>
+ <source>There is not enough free space at %1 to extract the RomFS. Please free up space or select a different dump directory at Emulation &gt; Configure &gt; System &gt; Filesystem &gt; Dump Root</source>
+ <translation>В %1 недостатньо вільного місця для вилучення RomFS. Будь ласка, звільніть місце або виберіть іншу папку для дампа в Емуляція &gt; Налаштування &gt; Система &gt; Файлова система &gt; Корінь дампа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2313"/>
+ <source>Extracting RomFS...</source>
+ <translation>Вилучення RomFS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2313"/>
+ <location filename="../../src/yuzu/main.cpp" line="2499"/>
+ <source>Cancel</source>
+ <translation>Скасувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2320"/>
+ <source>RomFS Extraction Succeeded!</source>
+ <translation>Вилучення RomFS пройшло успішно!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2321"/>
+ <source>The operation completed successfully.</source>
+ <translation>Операція завершилася успішно.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2365"/>
+ <source>Error Opening %1</source>
+ <translation>Помилка відкриття %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2374"/>
+ <source>Select Directory</source>
+ <translation>Обрати папку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2401"/>
+ <source>Properties</source>
+ <translation>Властивості</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2402"/>
+ <source>The game properties could not be loaded.</source>
+ <translation>Не вдалося завантажити властивості гри.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2419"/>
+ <source>Switch Executable (%1);;All Files (*.*)</source>
+ <comment>%1 is an identifier for the Switch executable file extensions.</comment>
+ <translation>Виконуваний файл Switch (%1);;Усі файли (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2423"/>
+ <source>Load File</source>
+ <translation>Завантажити файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2436"/>
+ <source>Open Extracted ROM Directory</source>
+ <translation>Відкрити папку вилученого ROM&apos;а</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2447"/>
+ <source>Invalid Directory Selected</source>
+ <translation>Вибрано неприпустиму папку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2448"/>
+ <source>The directory you have selected does not contain a &apos;main&apos; file.</source>
+ <translation>Папка, яку ви вибрали, не містить файлу &apos;main&apos;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2458"/>
+ <source>Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive (*.nca);;Nintendo Submission Package (*.nsp);;NX Cartridge Image (*.xci)</source>
+ <translation>Встановлюваний файл Switch (*.nca, *.nsp, *.xci);;Архів контенту Nintendo (*.nca);;Пакет подачі Nintendo (*.nsp);;Образ картриджа NX (*.xci)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2463"/>
+ <source>Install Files</source>
+ <translation>Встановити файли</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/main.cpp" line="2507"/>
+ <source>%n file(s) remaining</source>
+ <translation><numerusform>Залишився %n файл</numerusform><numerusform>Залишилося %n файл(ів)</numerusform><numerusform>Залишилося %n файл(ів)</numerusform><numerusform>Залишилося %n файл(ів)</numerusform></translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2509"/>
+ <source>Installing file &quot;%1&quot;...</source>
+ <translation>Встановлення файлу &quot;%1&quot;...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2555"/>
+ <location filename="../../src/yuzu/main.cpp" line="2569"/>
+ <source>Install Results</source>
+ <translation>Результати встановлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2556"/>
+ <source>To avoid possible conflicts, we discourage users from installing base games to the NAND.
+Please, only use this feature to install updates and DLC.</source>
+ <translation>Щоб уникнути можливих конфліктів, ми не рекомендуємо користувачам встановлювати ігри в NAND.
+Будь ласка, використовуйте цю функцію тільки для встановлення оновлень і завантажуваного контенту.</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/main.cpp" line="2562"/>
+ <source>%n file(s) were newly installed
+</source>
+ <translation><numerusform>%n файл було нещодавно встановлено
+</numerusform><numerusform>%n файл(ів) було нещодавно встановлено
+</numerusform><numerusform>%n файл(ів) було нещодавно встановлено
+</numerusform><numerusform>%n файл(ів) було нещодавно встановлено
+</numerusform></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/main.cpp" line="2565"/>
+ <source>%n file(s) were overwritten
+</source>
+ <translation><numerusform>%n файл було перезаписано
+</numerusform><numerusform>%n файл(ів) було перезаписано
+</numerusform><numerusform>%n файл(ів) було перезаписано
+</numerusform><numerusform>%n файл(ів) було перезаписано
+</numerusform></translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/main.cpp" line="2567"/>
+ <source>%n file(s) failed to install
+</source>
+ <translation><numerusform>%n файл не вдалося встановити
+</numerusform><numerusform>%n файл(ів) не вдалося встановити
+</numerusform><numerusform>%n файл(ів) не вдалося встановити
+</numerusform><numerusform>%n файл(ів) не вдалося встановити
+</numerusform></translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2668"/>
+ <source>System Application</source>
+ <translation>Системний додаток</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2669"/>
+ <source>System Archive</source>
+ <translation>Системний архів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2670"/>
+ <source>System Application Update</source>
+ <translation>Оновлення системного додатку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2671"/>
+ <source>Firmware Package (Type A)</source>
+ <translation>Пакет прошивки (Тип А)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2672"/>
+ <source>Firmware Package (Type B)</source>
+ <translation>Пакет прошивки (Тип Б)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2673"/>
+ <source>Game</source>
+ <translation>Гра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2674"/>
+ <source>Game Update</source>
+ <translation>Оновлення гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2675"/>
+ <source>Game DLC</source>
+ <translation>DLC до гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2676"/>
+ <source>Delta Title</source>
+ <translation>Дельта-титул</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2679"/>
+ <source>Select NCA Install Type...</source>
+ <translation>Виберіть тип установки NCA...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2680"/>
+ <source>Please select the type of title you would like to install this NCA as:
+(In most instances, the default &apos;Game&apos; is fine.)</source>
+ <translation>Будь ласка, виберіть тип додатку, який ви хочете встановити для цього NCA:
+(У більшості випадків, підходить стандартний вибір &quot;Гра&quot;.)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2686"/>
+ <source>Failed to Install</source>
+ <translation>Помилка встановлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2687"/>
+ <source>The title type you selected for the NCA is invalid.</source>
+ <translation>Тип додатку, який ви вибрали для NCA, недійсний.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2722"/>
+ <source>File not found</source>
+ <translation>Файл не знайдено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2723"/>
+ <source>File &quot;%1&quot; not found</source>
+ <translation>Файл &quot;%1&quot; не знайдено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2798"/>
+ <source>OK</source>
+ <translation>ОК</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2812"/>
+ <source>Missing yuzu Account</source>
+ <translation>Відсутній обліковий запис yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2813"/>
+ <source>In order to submit a game compatibility test case, you must link your yuzu account.&lt;br&gt;&lt;br/&gt;To link your yuzu account, go to Emulation &amp;gt; Configuration &amp;gt; Web.</source>
+ <translation>Щоб надіслати звіт про сумісність гри, необхідно прив&apos;язати свій обліковий запис yuzu. &lt;br&gt;&lt;br/&gt;Щоб прив&apos;язати свій обліковий запис yuzu, перейдіть у розділ Емуляція &amp;gt; Параметри &amp;gt; Мережа.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2823"/>
+ <source>Error opening URL</source>
+ <translation>Помилка під час відкриття URL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="2824"/>
+ <source>Unable to open the URL &quot;%1&quot;.</source>
+ <translation>Не вдалося відкрити URL: &quot;%1&quot;.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3120"/>
+ <source>TAS Recording</source>
+ <translation>Запис TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3121"/>
+ <source>Overwrite file of player 1?</source>
+ <translation>Перезаписати файл гравця 1?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3147"/>
+ <source>Invalid config detected</source>
+ <translation>Виявлено неприпустиму конфігурацію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3148"/>
+ <source>Handheld controller can&apos;t be used on docked mode. Pro controller will be selected.</source>
+ <translation>Портативний контролер не може бути використаний у режимі док-станції. Буде обрано контролер Pro.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3243"/>
+ <location filename="../../src/yuzu/main.cpp" line="3271"/>
+ <source>Amiibo</source>
+ <translation>Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3243"/>
+ <location filename="../../src/yuzu/main.cpp" line="3271"/>
+ <source>The current amiibo has been removed</source>
+ <translation>Поточний amiibo було прибрано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3248"/>
+ <source>Error</source>
+ <translation>Помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3248"/>
+ <location filename="../../src/yuzu/main.cpp" line="3283"/>
+ <source>The current game is not looking for amiibos</source>
+ <translation>Поточна гра не шукає amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3254"/>
+ <source>Amiibo File (%1);; All Files (*.*)</source>
+ <translation>Файл Amiibo (%1);; Всі Файли (*.*)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3255"/>
+ <source>Load Amiibo</source>
+ <translation>Завантажити Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3267"/>
+ <source>Error loading Amiibo data</source>
+ <translation>Помилка під час завантаження даних Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3277"/>
+ <source>The selected file is not a valid amiibo</source>
+ <translation>Обраний файл не є допустимим amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3280"/>
+ <source>The selected file is already on use</source>
+ <translation>Обраний файл уже використовується</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3286"/>
+ <source>An unknown error occurred</source>
+ <translation>Виникла невідома помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3338"/>
+ <source>Capture Screenshot</source>
+ <translation>Зробити знімок екрану</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3339"/>
+ <source>PNG Image (*.png)</source>
+ <translation>Зображення PNG (*.png)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3405"/>
+ <source>TAS state: Running %1/%2</source>
+ <translation>Стан TAS: Виконується %1/%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3407"/>
+ <source>TAS state: Recording %1</source>
+ <translation>Стан TAS: Записується %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3409"/>
+ <source>TAS state: Idle %1/%2</source>
+ <translation>Стан TAS: Простий %1/%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3411"/>
+ <source>TAS State: Invalid</source>
+ <translation>Стан TAS: Неприпустимий</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3425"/>
+ <source>&amp;Stop Running</source>
+ <translation>[&amp;S] Зупинка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3425"/>
+ <source>&amp;Start</source>
+ <translation>[&amp;S] Почати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3426"/>
+ <source>Stop R&amp;ecording</source>
+ <translation>[&amp;E] Закінчити запис</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3426"/>
+ <source>R&amp;ecord</source>
+ <translation>[&amp;E] Запис</translation>
+ </message>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/main.cpp" line="3450"/>
+ <source>Building: %n shader(s)</source>
+ <translation><numerusform>Побудова: %n шейдер</numerusform><numerusform>Побудова: %n шейдер(ів)</numerusform><numerusform>Побудова: %n шейдер(ів)</numerusform><numerusform>Побудова: %n шейдер(ів)</numerusform></translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3459"/>
+ <source>Scale: %1x</source>
+ <comment>%1 is the resolution scaling factor</comment>
+ <translation>Масштаб: %1x</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3462"/>
+ <source>Speed: %1% / %2%</source>
+ <translation>Швидкість: %1% / %2%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3466"/>
+ <source>Speed: %1%</source>
+ <translation>Швидкість: %1%</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3470"/>
+ <source>Game: %1 FPS (Unlocked)</source>
+ <translation>Гра: %1 FPS (Необмежено)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3473"/>
+ <source>Game: %1 FPS</source>
+ <translation>Гра: %1 FPS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3475"/>
+ <source>Frame: %1 ms</source>
+ <translation>Кадр: %1 мс</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3486"/>
+ <source>GPU NORMAL</source>
+ <translation>ГП НОРМАЛЬНО</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3491"/>
+ <source>GPU HIGH</source>
+ <translation>ГП ВИСОКО</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3496"/>
+ <source>GPU EXTREME</source>
+ <translation>ГП ЕКСТРИМ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3501"/>
+ <source>GPU ERROR</source>
+ <translation>ГП ПОМИЛКА</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3510"/>
+ <source>DOCKED</source>
+ <translation>В ДОК-СТАНЦІЇ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3510"/>
+ <source>HANDHELD</source>
+ <translation>ПОРТАТИВНИЙ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3517"/>
+ <source>NEAREST</source>
+ <translation>НАЙБЛИЖЧІЙ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3520"/>
+ <location filename="../../src/yuzu/main.cpp" line="3535"/>
+ <source>BILINEAR</source>
+ <translation>БІЛІНІЙНИЙ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3523"/>
+ <source>BICUBIC</source>
+ <translation>БІКУБІЧНИЙ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3526"/>
+ <source>GAUSSIAN</source>
+ <translation>ГАУС</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3529"/>
+ <source>SCALEFORCE</source>
+ <translation>SCALEFORCE</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3532"/>
+ <source>FSR</source>
+ <translation>FSR</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3544"/>
+ <location filename="../../src/yuzu/main.cpp" line="3550"/>
+ <source>NO AA</source>
+ <translation>БЕЗ ЗГЛАДЖУВАННЯ</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3547"/>
+ <source>FXAA</source>
+ <translation>FXAA</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3624"/>
+ <source>The game you are trying to load requires additional files from your Switch to be dumped before playing.&lt;br/&gt;&lt;br/&gt;For more information on dumping these files, please see the following wiki page: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Dumping System Archives and the Shared Fonts from a Switch Console&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>Гра, яку ви намагаєтеся завантажити, вимагає, щоб додаткові файли були здамплені з вашого Switch перед початком гри. &lt;br/&gt;&lt;br/&gt;Для отримання додаткової інформації про дамп цих файлів див. наступну вікі: &lt;a href=&apos;https://yuzu-emu.org/wiki/dumping-system-archives-and-the-shared-fonts-from-a-switch-console/&apos;&gt;Дамп системних архівів і загальних шрифтів з консолі&lt;/a&gt;. &lt;br/&gt;&lt;br/&gt;Хочете повернутися до списку ігор? Продовження емуляції може призвести до збоїв, пошкодження збережених даних або інших помилок.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3639"/>
+ <source>yuzu was unable to locate a Switch system archive. %1</source>
+ <translation>yuzu не вдалося знайти системний архів Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3641"/>
+ <source>yuzu was unable to locate a Switch system archive: %1. %2</source>
+ <translation>yuzu не вдалося знайти системний архів Switch: %1. %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3645"/>
+ <source>System Archive Not Found</source>
+ <translation>Системний архів не знайдено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3647"/>
+ <source>System Archive Missing</source>
+ <translation>Відсутній системний архів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3653"/>
+ <source>yuzu was unable to locate the Switch shared fonts. %1</source>
+ <translation>yuzu не вдалося знайти загальні шрифти Switch. %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3654"/>
+ <source>Shared Fonts Not Found</source>
+ <translation>Загальні шрифти не знайдено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3656"/>
+ <source>Shared Font Missing</source>
+ <translation>Загальні шрифти відсутні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3662"/>
+ <source>Fatal Error</source>
+ <translation>Фатальна помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3663"/>
+ <source>yuzu has encountered a fatal error, please see the log for more details. For more information on accessing the log, please see the following page: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;How to Upload the Log File&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Would you like to quit back to the game list? Continuing emulation may result in crashes, corrupted save data, or other bugs.</source>
+ <translation>yuzu зіткнувся з фатальною помилкою, перевірте журнал для отримання більш детальної інформації. Для отримання додаткової інформації про доступ до журналу відкрийте наступну сторінку: &lt;a href=&apos;https://community.citra-emu.org/t/how-to-upload-the-log-file/296&apos;&gt;Як завантажити файл журналу&lt;/a&gt;.&lt;br/&gt;&lt;br/&gt;Ви хочете повернутися до списку ігор? Продовження емуляції може призвести до збоїв, пошкодження збережень або інших помилок.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3672"/>
+ <source>Fatal Error encountered</source>
+ <translation>Сталася фатальна помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3695"/>
+ <source>Confirm Key Rederivation</source>
+ <translation>Підтвердіть перерахунок ключа</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3696"/>
+ <source>You are about to force rederive all of your keys.
+If you do not know what this means or what you are doing,
+this is a potentially destructive action.
+Please make sure this is what you want
+and optionally make backups.
+
+This will delete your autogenerated key files and re-run the key derivation module.</source>
+ <translation>Ви збираєтеся примусово перерахувати всі ваші ключі.
+Якщо ви не знаєте, що це означає або що ви робите,
+це потенційно руйнівна дія.
+Будь ласка, переконайтеся, що це те, що ви хочете
+і, по бажанню, зробіть резервні копії.
+
+Це видалить ваші автоматично згенеровані файли ключів і повторно запустить модуль розрахунку ключів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3728"/>
+ <source>Missing fuses</source>
+ <translation>Відсутні запобіжники</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3731"/>
+ <source> - Missing BOOT0</source>
+ <translation>- Відсутній BOOT0</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3734"/>
+ <source> - Missing BCPKG2-1-Normal-Main</source>
+ <translation>- Відсутній BCPKG2-1-Normal-Main</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3737"/>
+ <source> - Missing PRODINFO</source>
+ <translation> - Відсутній PRODINFO</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3741"/>
+ <source>Derivation Components Missing</source>
+ <translation>Компоненти розрахунку відсутні</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3742"/>
+ <source>Encryption keys are missing. &lt;br&gt;Please follow &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;the yuzu quickstart guide&lt;/a&gt; to get all your keys, firmware and games.&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</source>
+ <translation>Ключі шифрування відсутні.&lt;br&gt;Будь ласка, дотримуйтесь &lt;a href=&apos;https://yuzu-emu.org/help/quickstart/&apos;&gt;короткого керівництва користувача yuzu&lt;/a&gt;, щоб отримати всі ваші ключі, прошивку та ігри&lt;br&gt;&lt;br&gt;&lt;small&gt;(%1)&lt;/small&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3751"/>
+ <source>Deriving keys...
+This may take up to a minute depending
+on your system&apos;s performance.</source>
+ <translation>Отримання ключів...
+Це може зайняти до хвилини залежно від
+від продуктивності вашої системи.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3753"/>
+ <source>Deriving Keys</source>
+ <translation>Отримання ключів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3798"/>
+ <source>Select RomFS Dump Target</source>
+ <translation>Оберіть ціль для дампа RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3799"/>
+ <source>Please select which RomFS you would like to dump.</source>
+ <translation>Будь ласка, виберіть, який RomFS ви хочете здампити.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3814"/>
+ <source>Are you sure you want to close yuzu?</source>
+ <translation>Ви впевнені, що хочете закрити yuzu?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3815"/>
+ <location filename="../../src/yuzu/main.cpp" line="3913"/>
+ <location filename="../../src/yuzu/main.cpp" line="3926"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3914"/>
+ <source>Are you sure you want to stop the emulation? Any unsaved progress will be lost.</source>
+ <translation>Ви впевнені, що хочете зупинити емуляцію? Будь-який незбережений прогрес буде втрачено.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.cpp" line="3923"/>
+ <source>The currently running application has requested yuzu to not exit.
+
+Would you like to bypass this and exit anyway?</source>
+ <translation>Запущений на даний момент додаток просить yuzu не завершувати роботу.
+
+Чи хочете ви обійти це і вийти в будь-якому випадку?</translation>
+ </message>
+</context>
+<context>
+ <name>GRenderWindow</name>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1047"/>
+ <source>OpenGL not available!</source>
+ <translation>OpenGL недоступний!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1048"/>
+ <source>yuzu has not been compiled with OpenGL support.</source>
+ <translation>yuzu не було зібрано з підтримкою OpenGL.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1067"/>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1087"/>
+ <source>Error while initializing OpenGL!</source>
+ <translation>Помилка під час ініціалізації OpenGL!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1068"/>
+ <source>Your GPU may not support OpenGL, or you do not have the latest graphics driver.</source>
+ <translation>Ваш ГП може не підтримувати OpenGL, або у вас встановлено застарілий графічний драйвер.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1077"/>
+ <source>Error while initializing OpenGL 4.6!</source>
+ <translation>Помилка під час ініціалізації OpenGL 4.6!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1078"/>
+ <source>Your GPU may not support OpenGL 4.6, or you do not have the latest graphics driver.&lt;br&gt;&lt;br&gt;GL Renderer:&lt;br&gt;%1</source>
+ <translation>Ваш ГП може не підтримувати OpenGL 4.6, або у вас встановлено застарілий графічний драйвер.&lt;br&gt;&lt;br&gt;Рендерер GL:&lt;br&gt;%1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/bootmanager.cpp" line="1088"/>
+ <source>Your GPU may not support one or more required OpenGL extensions. Please ensure you have the latest graphics driver.&lt;br&gt;&lt;br&gt;GL Renderer:&lt;br&gt;%1&lt;br&gt;&lt;br&gt;Unsupported extensions:&lt;br&gt;%2</source>
+ <translation>Ваш ГП може не підтримувати одне або кілька необхідних розширень OpenGL. Будь ласка, переконайтеся в тому, що у вас встановлено останній графічний драйвер.&lt;br&gt;&lt;br&gt;Рендерер GL:&lt;br&gt;%1&lt;br&gt;&lt;br&gt;Розширення, що не підтримуються:&lt;br&gt;%2</translation>
+ </message>
+</context>
+<context>
+ <name>GameList</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="532"/>
+ <source>Favorite</source>
+ <translation>Улюблені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="534"/>
+ <source>Start Game</source>
+ <translation>Запустити гру</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="536"/>
+ <source>Start Game without Custom Configuration</source>
+ <translation>Запустити гру без користувацького налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="538"/>
+ <source>Open Save Data Location</source>
+ <translation>Відкрити папку для збережень</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="539"/>
+ <source>Open Mod Data Location</source>
+ <translation>Відкрити папку для модів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="541"/>
+ <source>Open Transferable Pipeline Cache</source>
+ <translation>Відкрити переносний кеш конвеєра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="543"/>
+ <source>Remove</source>
+ <translation>Видалити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="544"/>
+ <source>Remove Installed Update</source>
+ <translation>Видалити встановлене оновлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="545"/>
+ <source>Remove All Installed DLC</source>
+ <translation>Видалити усі DLC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="546"/>
+ <source>Remove Custom Configuration</source>
+ <translation>Видалити користувацьке налаштування</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="547"/>
+ <source>Remove OpenGL Pipeline Cache</source>
+ <translation>Видалити кеш конвеєра OpenGL</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="548"/>
+ <source>Remove Vulkan Pipeline Cache</source>
+ <translation>Видалити кеш конвеєра Vulkan</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="550"/>
+ <source>Remove All Pipeline Caches</source>
+ <translation>Видалити весь кеш конвеєра </translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="551"/>
+ <source>Remove All Installed Contents</source>
+ <translation>Видалити весь встановлений вміст</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="552"/>
+ <location filename="../../src/yuzu/game_list.cpp" line="553"/>
+ <source>Dump RomFS</source>
+ <translation>Дамп RomFS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="554"/>
+ <source>Dump RomFS to SDMC</source>
+ <translation>Здампити RomFS у SDMC</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="555"/>
+ <source>Copy Title ID to Clipboard</source>
+ <translation>Скопіювати ідентифікатор додатку в буфер обміну</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="556"/>
+ <source>Navigate to GameDB entry</source>
+ <translation>Перейти до сторінки GameDB</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="558"/>
+ <source>Properties</source>
+ <translation>Властивості</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="630"/>
+ <source>Scan Subfolders</source>
+ <translation>Сканувати підпапки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="631"/>
+ <source>Remove Game Directory</source>
+ <translation>Видалити директорію гри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="650"/>
+ <source>▲ Move Up</source>
+ <translation>▲ Перемістити вверх</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="651"/>
+ <source>▼ Move Down</source>
+ <translation>▼ Перемістити вниз</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="652"/>
+ <source>Open Directory Location</source>
+ <translation>Відкрити розташування папки</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="697"/>
+ <source>Clear</source>
+ <translation>Очистити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="761"/>
+ <source>Name</source>
+ <translation>Назва</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="762"/>
+ <source>Compatibility</source>
+ <translation>Сумісність</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="763"/>
+ <source>Add-ons</source>
+ <translation>Доповнення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="764"/>
+ <source>File type</source>
+ <translation>Тип файлу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="765"/>
+ <source>Size</source>
+ <translation>Розмір</translation>
+ </message>
+</context>
+<context>
+ <name>GameListItemCompat</name>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Perfect</source>
+ <translation>Ідеально</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="149"/>
+ <source>Game functions flawless with no audio or graphical glitches, all tested functionality works as intended without
+any workarounds needed.</source>
+ <translation>Гра працює бездоганно, без звукових або графічних артефактів, усі протестовані функції працюють без обхідних шляхів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Great</source>
+ <translation>Чудово</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="150"/>
+ <source>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some
+workarounds.</source>
+ <translation>Гра працює з невеликими графічними або звуковими артефактами і може бути пройдена від
+ початку до кінця. Можуть знадобитися обхідні шляхи.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Okay</source>
+ <translation>Добре</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="151"/>
+ <source>Game functions with major graphical or audio glitches, but game is playable from start to finish with
+workarounds.</source>
+ <translation>Гра працює з істотними графічними або звуковими артефактами, але може бути пройдена
+з використанням обхідних шляхів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Bad</source>
+ <translation>Погано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="152"/>
+ <source>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches
+even with workarounds.</source>
+ <translation>Гра працює, але з істотними графічними або звуковими артефактами.
+У деяких частинах неможливо просунутися навіть з обхідними шляхами.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="153"/>
+ <source>Intro/Menu</source>
+ <translation>Вступ/Меню</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="153"/>
+ <source>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start
+Screen.</source>
+ <translation>У гру неможливо грати через графічні або звукові артефакти.
+Неможливо просунутися далі стартового екрана.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="154"/>
+ <source>Won&apos;t Boot</source>
+ <translation>Не запускається</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="154"/>
+ <source>The game crashes when attempting to startup.</source>
+ <translation>Гра вилітає під час запуску.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="155"/>
+ <source>Not Tested</source>
+ <translation>Не перевірено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="155"/>
+ <source>The game has not yet been tested.</source>
+ <translation>Гру ще не перевіряли на сумісність.</translation>
+ </message>
+</context>
+<context>
+ <name>GameListPlaceholder</name>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="935"/>
+ <source>Double-click to add a new folder to the game list</source>
+ <translation>Натисніть двічі, щоб додати нову папку до списку ігор</translation>
+ </message>
+</context>
+<context>
+ <name>GameListSearchField</name>
+ <message numerus="yes">
+ <location filename="../../src/yuzu/game_list.cpp" line="86"/>
+ <source>%1 of %n result(s)</source>
+ <translation><numerusform>%1 із %n результат(ів)</numerusform><numerusform>%1 із %n результат(ів)</numerusform><numerusform>%1 із %n результат(ів)</numerusform><numerusform>%1 із %n результат(ів)</numerusform></translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="777"/>
+ <source>Filter:</source>
+ <translation>Пошук:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list.cpp" line="778"/>
+ <source>Enter pattern to filter</source>
+ <translation>Введіть текст для пошуку</translation>
+ </message>
+</context>
+<context>
+ <name>HostRoom</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="14"/>
+ <source>Create Room</source>
+ <translation>Створити кімнату</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="37"/>
+ <source>Room Name</source>
+ <translation>Назва кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="51"/>
+ <source>Preferred Game</source>
+ <translation>Переважна гра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="61"/>
+ <source>Max Players</source>
+ <translation>Максимальна кількість гравців</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="91"/>
+ <source>Username</source>
+ <translation>Ім&apos;я користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="101"/>
+ <source>(Leave blank for open game)</source>
+ <translation>(Залиште порожнім для відкритої гри)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="118"/>
+ <source>Password</source>
+ <translation>Пароль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="125"/>
+ <source>Port</source>
+ <translation>Порт</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="139"/>
+ <source>Room Description</source>
+ <translation>Опис кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="153"/>
+ <source>Load Previous Ban List</source>
+ <translation>Завантажити попередній список заблокованих</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="184"/>
+ <source>Public</source>
+ <translation>Публічна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="189"/>
+ <source>Unlisted</source>
+ <translation>Прихована</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.ui" line="197"/>
+ <source>Host Room</source>
+ <translation>Створити кімнату</translation>
+ </message>
+</context>
+<context>
+ <name>HostRoomWindow</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.cpp" line="182"/>
+ <source>Error</source>
+ <translation>Помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/host_room.cpp" line="183"/>
+ <source>Failed to announce the room to the public lobby. In order to host a room publicly, you must have a valid yuzu account configured in Emulation -&gt; Configure -&gt; Web. If you do not want to publish a room in the public lobby, then select Unlisted instead.
+Debug Message: </source>
+ <translation>Не вдалося оголосити кімнату в публічному фойє. Щоб хостити публічну кімнату, у вас має бути діючий обліковий запис yuzu, налаштований в Емуляція -&gt; Налаштування -&gt; Мережа. Якщо ви не хочете оголошувати кімнату в публічному лобі, виберіть замість цього прихований тип.
+Повідомлення налагодження:</translation>
+ </message>
+</context>
+<context>
+ <name>Hotkeys</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="73"/>
+ <source>Audio Mute/Unmute</source>
+ <translation>Увімкнення/вимкнення звуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="73"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="74"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="75"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="76"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="77"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="78"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="80"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="82"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="83"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="84"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="85"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="86"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="88"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="90"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="92"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="93"/>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="94"/>
+ <source>Main Window</source>
+ <translation>Основне вікно</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="74"/>
+ <source>Audio Volume Down</source>
+ <translation>Зменшити гучність звуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="75"/>
+ <source>Audio Volume Up</source>
+ <translation>Підвищити гучність звуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="76"/>
+ <source>Capture Screenshot</source>
+ <translation>Зробити знімок екрану</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="77"/>
+ <source>Change Adapting Filter</source>
+ <translation>Змінити адаптуючий фільтр</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="78"/>
+ <source>Change Docked Mode</source>
+ <translation>Змінити режим консолі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="79"/>
+ <source>Change GPU Accuracy</source>
+ <translation>Змінити точність ГП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="80"/>
+ <source>Continue/Pause Emulation</source>
+ <translation>Продовження/Пауза емуляції</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="81"/>
+ <source>Exit Fullscreen</source>
+ <translation>Вийти з повноекранного режиму</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="82"/>
+ <source>Exit yuzu</source>
+ <translation>Вийти з yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="83"/>
+ <source>Fullscreen</source>
+ <translation>Повний екран</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="84"/>
+ <source>Load File</source>
+ <translation>Завантажити файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="85"/>
+ <source>Load/Remove Amiibo</source>
+ <translation>Завантажити/видалити Amiibo</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="86"/>
+ <source>Restart Emulation</source>
+ <translation>Перезапустити емуляцію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="87"/>
+ <source>Stop Emulation</source>
+ <translation>Зупинити емуляцію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="88"/>
+ <source>TAS Record</source>
+ <translation>Запис TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="89"/>
+ <source>TAS Reset</source>
+ <translation>Скидання TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="90"/>
+ <source>TAS Start/Stop</source>
+ <translation>Старт/Стоп TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="91"/>
+ <source>Toggle Filter Bar</source>
+ <translation>Переключити панель пошуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="92"/>
+ <source>Toggle Framerate Limit</source>
+ <translation>Переключити обмеження частоти кадрів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="93"/>
+ <source>Toggle Mouse Panning</source>
+ <translation>Переключити панорамування миші</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/config.cpp" line="94"/>
+ <source>Toggle Status Bar</source>
+ <translation>Переключити панель стану</translation>
+ </message>
+</context>
+<context>
+ <name>InstallDialog</name>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="29"/>
+ <source>Please confirm these are the files you wish to install.</source>
+ <translation>Будь ласка, переконайтеся, що це ті файли, які ви хочете встановити.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="32"/>
+ <source>Installing an Update or DLC will overwrite the previously installed one.</source>
+ <translation>Встановлення оновлення або завантажуваного контенту перезапише раніше встановлене.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="36"/>
+ <source>Install</source>
+ <translation>Встановити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/install_dialog.cpp" line="50"/>
+ <source>Install Files to NAND</source>
+ <translation>Встановити файли в NAND</translation>
+ </message>
+</context>
+<context>
+ <name>LimitableInputDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/limitable_input_dialog.cpp" line="61"/>
+ <source>The text can't contain any of the following characters:
+%1</source>
+ <translation>У тексті неприпустимі такі символи:
+%1</translation>
+ </message>
+</context>
+<context>
+ <name>LoadingScreen</name>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="84"/>
+ <source>Loading Shaders 387 / 1628</source>
+ <translation>Завантаження шейдерів 387 / 1628</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="121"/>
+ <source>Loading Shaders %v out of %m</source>
+ <translation>Завантаження шейдерів %v із %m</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.ui" line="135"/>
+ <source>Estimated Time 5m 4s</source>
+ <translation>Залишилося приблизно 5м 4с</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="83"/>
+ <source>Loading...</source>
+ <translation>Завантаження...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="84"/>
+ <source>Loading Shaders %1 / %2</source>
+ <translation>Завантаження шейдерів %1 / %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="85"/>
+ <source>Launching...</source>
+ <translation>Запуск...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/loading_screen.cpp" line="170"/>
+ <source>Estimated Time %1</source>
+ <translation>Залишилося приблизно %1</translation>
+ </message>
+</context>
+<context>
+ <name>Lobby</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="14"/>
+ <source>Public Room Browser</source>
+ <translation>Браузер публічних кімнат</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="32"/>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="39"/>
+ <source>Nickname</source>
+ <translation>Псевдонім</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="59"/>
+ <source>Filters</source>
+ <translation>Фільтри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="66"/>
+ <source>Search</source>
+ <translation>Пошук</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="76"/>
+ <source>Games I Own</source>
+ <translation>Ігри, якими я володію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="83"/>
+ <source>Hide Full Rooms</source>
+ <translation>Приховати повні кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.ui" line="103"/>
+ <source>Refresh Lobby</source>
+ <translation>Оновити фойє</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="112"/>
+ <source>Password Required to Join</source>
+ <translation>Для входу необхідний пароль</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="112"/>
+ <source>Password:</source>
+ <translation>Пароль:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="215"/>
+ <source>Players</source>
+ <translation>Гравці</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="216"/>
+ <source>Room Name</source>
+ <translation>Назва кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="217"/>
+ <source>Preferred Game</source>
+ <translation>Переважна гра</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="218"/>
+ <source>Host</source>
+ <translation>Хост</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="225"/>
+ <source>Refreshing</source>
+ <translation>Оновлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby.cpp" line="282"/>
+ <source>Refresh List</source>
+ <translation>Оновити список</translation>
+ </message>
+</context>
+<context>
+ <name>MainWindow</name>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="14"/>
+ <source>yuzu</source>
+ <translation>yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="44"/>
+ <source>&amp;File</source>
+ <translation>[&amp;F] Файл</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="48"/>
+ <source>&amp;Recent Files</source>
+ <translation>[&amp;R] Нещодавні файли</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="67"/>
+ <source>&amp;Emulation</source>
+ <translation>[&amp;E] Емуляція</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="78"/>
+ <source>&amp;View</source>
+ <translation>[&amp;V] Вигляд</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="82"/>
+ <source>&amp;Reset Window Size</source>
+ <translation>[&amp;R] Скинути розмір вікна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="87"/>
+ <source>&amp;Debugging</source>
+ <translation>[&amp;D] Налагодження</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="92"/>
+ <source>Reset Window Size to &amp;720p</source>
+ <translation>Скинути розмір вікна до &amp;720p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="95"/>
+ <source>Reset Window Size to 720p</source>
+ <translation>Скинути розмір вікна до 720p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="100"/>
+ <source>Reset Window Size to &amp;900p</source>
+ <translation>Скинути розмір вікна до &amp;900p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="103"/>
+ <source>Reset Window Size to 900p</source>
+ <translation>Скинути розмір вікна до 900p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="108"/>
+ <source>Reset Window Size to &amp;1080p</source>
+ <translation>Скинути розмір вікна до &amp;1080p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="111"/>
+ <source>Reset Window Size to 1080p</source>
+ <translation>Скинути розмір вікна до 1080p</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="128"/>
+ <source>&amp;Multiplayer</source>
+ <translation>[&amp;M] Мультиплеєр</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="139"/>
+ <source>&amp;Tools</source>
+ <translation>[&amp;T] Інструменти</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="143"/>
+ <source>&amp;TAS</source>
+ <translation>[&amp;T] TAS</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="158"/>
+ <source>&amp;Help</source>
+ <translation>[&amp;H] Допомога</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="179"/>
+ <source>&amp;Install Files to NAND...</source>
+ <translation>[&amp;I] Встановити файли в NAND...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="184"/>
+ <source>L&amp;oad File...</source>
+ <translation>[&amp;O] Завантажити файл...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="189"/>
+ <source>Load &amp;Folder...</source>
+ <translation>[&amp;F] Завантажити папку...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="194"/>
+ <source>E&amp;xit</source>
+ <translation>[&amp;X] Вихід</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="202"/>
+ <source>&amp;Pause</source>
+ <translation>[&amp;P] Пауза</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="210"/>
+ <source>&amp;Stop</source>
+ <translation>[&amp;S] Стоп</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="215"/>
+ <source>&amp;Reinitialize keys...</source>
+ <translation>[&amp;R] Переініціалізувати ключі...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="220"/>
+ <source>&amp;About yuzu</source>
+ <translation>[&amp;A] Про yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="228"/>
+ <source>Single &amp;Window Mode</source>
+ <translation>[&amp;W] Режим одного вікна</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="233"/>
+ <source>Con&amp;figure...</source>
+ <translation>[&amp;F] Налаштування...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="241"/>
+ <source>Display D&amp;ock Widget Headers</source>
+ <translation>[&amp;O] Відображати заголовки віджетів дока</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="249"/>
+ <source>Show &amp;Filter Bar</source>
+ <translation>[&amp;F] Показати панель пошуку</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="257"/>
+ <source>Show &amp;Status Bar</source>
+ <translation>[&amp;S] Показати панель статусу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="260"/>
+ <source>Show Status Bar</source>
+ <translation>Показати панель статусу</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="268"/>
+ <source>&amp;Browse Public Game Lobby</source>
+ <translation>[&amp;B] Переглянути публічні ігрові фойє</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="276"/>
+ <source>&amp;Create Room</source>
+ <translation>[&amp;C] Створити кімнату</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="284"/>
+ <source>&amp;Leave Room</source>
+ <translation>[&amp;L] Залишити кімнату</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="289"/>
+ <source>&amp;Direct Connect to Room</source>
+ <translation>[&amp;D] Пряме під&apos;єднання до кімнати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="297"/>
+ <source>&amp;Show Current Room</source>
+ <translation>[&amp;S] Показати поточну кімнату</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="305"/>
+ <source>F&amp;ullscreen</source>
+ <translation>[&amp;U] Повноекранний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="313"/>
+ <source>&amp;Restart</source>
+ <translation>[&amp;R] Перезапустити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="321"/>
+ <source>Load/Remove &amp;Amiibo...</source>
+ <translation>[&amp;A] Завантажити/Видалити Amiibo...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="329"/>
+ <source>&amp;Report Compatibility</source>
+ <translation>[&amp;R] Повідомити про сумісність</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="337"/>
+ <source>Open &amp;Mods Page</source>
+ <translation>[&amp;M] Відкрити сторінку модів</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="342"/>
+ <source>Open &amp;Quickstart Guide</source>
+ <translation>[&amp;Q] Відкрити посібник користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="347"/>
+ <source>&amp;FAQ</source>
+ <translation>[&amp;F] ЧАП</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="352"/>
+ <source>Open &amp;yuzu Folder</source>
+ <translation>[&amp;Y] Відкрити папку yuzu</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="360"/>
+ <source>&amp;Capture Screenshot</source>
+ <translation>[&amp;C] Зробити знімок екрану</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="365"/>
+ <source>&amp;Configure TAS...</source>
+ <translation>[&amp;C] Налаштування TAS...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="373"/>
+ <source>Configure C&amp;urrent Game...</source>
+ <translation>[&amp;U] Налаштувати поточну гру...</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="381"/>
+ <source>&amp;Start</source>
+ <translation>[&amp;S] Почати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="389"/>
+ <source>&amp;Reset</source>
+ <translation>[&amp;S] Скинути</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/main.ui" line="397"/>
+ <source>R&amp;ecord</source>
+ <translation>[&amp;E] Запис</translation>
+ </message>
+</context>
+<context>
+ <name>MicroProfileDialog</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/profiler.cpp" line="50"/>
+ <source>&amp;MicroProfile</source>
+ <translation>[&amp;M] MicroProfile</translation>
+ </message>
+</context>
+<context>
+ <name>ModerationDialog</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="6"/>
+ <source>Moderation</source>
+ <translation>Модерація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="20"/>
+ <source>Ban List</source>
+ <translation>Список заблокованих</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="41"/>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="73"/>
+ <source>Refreshing</source>
+ <translation>Оновлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.ui" line="51"/>
+ <source>Unban</source>
+ <translation>Розблокувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="40"/>
+ <source>Subject</source>
+ <translation>Суб&apos;єкт</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="41"/>
+ <source>Type</source>
+ <translation>Тип</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="83"/>
+ <source>Forum Username</source>
+ <translation>Ім&apos;я користувача на форумі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="88"/>
+ <source>IP Address</source>
+ <translation>IP-адреса</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/moderation_dialog.cpp" line="95"/>
+ <source>Refresh</source>
+ <translation>Оновити</translation>
+ </message>
+</context>
+<context>
+ <name>MultiplayerState</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="90"/>
+ <source>Current connection status</source>
+ <translation>Поточний стан з&apos;єднання</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="117"/>
+ <source>Not Connected. Click here to find a room!</source>
+ <translation>Не з&apos;єднано. Натисніть тут, щоб знайти кімнату!</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="123"/>
+ <source>Not Connected</source>
+ <translation>Не з&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="129"/>
+ <source>Connected</source>
+ <translation>З&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="136"/>
+ <source>New Messages Received</source>
+ <translation>Отримано нові повідомлення</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="207"/>
+ <source>Error</source>
+ <translation>Помилка</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/state.cpp" line="208"/>
+ <source>Failed to update the room information. Please check your Internet connection and try hosting the room again.
+Debug Message: </source>
+ <translation>Не вдалося оновити інформацію про кімнату. Будь ласка, перевірте підключення до Інтернету та спробуйте знову зайти в кімнату.
+Повідомлення налагодження:</translation>
+ </message>
+</context>
+<context>
+ <name>NetworkMessage</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="11"/>
+ <source>Username is not valid. Must be 4 to 20 alphanumeric characters.</source>
+ <translation>Ім&apos;я користувача неприпустиме. Має бути від 4 до 20 буквено-цифрових символів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="13"/>
+ <source>Room name is not valid. Must be 4 to 20 alphanumeric characters.</source>
+ <translation>Назва кімнати неприпустима. Має бути від 4 до 20 буквено-цифрових символів.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="15"/>
+ <source>Username is already in use or not valid. Please choose another.</source>
+ <translation>Ім&apos;я користувача вже використовується або недійсне. Будь ласка, виберіть інше.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="17"/>
+ <source>IP is not a valid IPv4 address.</source>
+ <translation>IP-адреса не є дійсною адресою IPv4.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="19"/>
+ <source>Port must be a number between 0 to 65535.</source>
+ <translation>Порт повинен бути числом від 0 до 65535.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="20"/>
+ <source>You must choose a Preferred Game to host a room. If you do not have any games in your game list yet, add a game folder by clicking on the plus icon in the game list.</source>
+ <translation>Ви повинні вибрати бажану гру, щоб хостити кімнату. Якщо у вашому списку ігор ще немає жодної гри, додайте папку з грою, натиснувши на значок плюса у списку ігор.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="24"/>
+ <source>Unable to find an internet connection. Check your internet settings.</source>
+ <translation>Неможливо знайти підключення до Інтернету. Перевірте налаштування інтернету.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="26"/>
+ <source>Unable to connect to the host. Verify that the connection settings are correct. If you still cannot connect, contact the room host and verify that the host is properly configured with the external port forwarded.</source>
+ <translation>Неможливо підключитися до хоста. Перевірте правильність налаштувань підключення. Якщо під&apos;єднання, як і раніше, неможливе, зв&apos;яжіться з хостом кімнати та переконайтеся, що хост правильно налаштований із прокинутим зовнішнім портом.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="30"/>
+ <source>Unable to connect to the room because it is already full.</source>
+ <translation>Неможливо підключитися до кімнати, оскільки вона вже заповнена.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="32"/>
+ <source>Creating a room failed. Please retry. Restarting yuzu might be necessary.</source>
+ <translation>Створення кімнати не вдалося. Будь ласка, повторіть спробу. Можливо, потрібно перезапустити yuzu.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="34"/>
+ <source>The host of the room has banned you. Speak with the host to unban you or try a different room.</source>
+ <translation>Хост кімнати заблокував вас. Поговоріть із хостом, щоб він розблокував вас, або спробуйте іншу кімнату.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="37"/>
+ <source>Version mismatch! Please update to the latest version of yuzu. If the problem persists, contact the room host and ask them to update the server.</source>
+ <translation>Невідповідність версій! Будь ласка, оновіть yuzu до останньої версії. Якщо проблема не зникне, зверніться до хосту кімнати і попросіть його оновити сервер.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="39"/>
+ <source>Incorrect password.</source>
+ <translation>Невірний пароль.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="40"/>
+ <source>An unknown error occurred. If this error continues to occur, please open an issue</source>
+ <translation>Сталася невідома помилка. Якщо ця помилка продовжує виникати, будь ласка, відкрийте проблему</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="43"/>
+ <source>Connection to room lost. Try to reconnect.</source>
+ <translation>З&apos;єднання з кімнатою втрачено. Спробуйте підключитися знову.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="45"/>
+ <source>You have been kicked by the room host.</source>
+ <translation>Вас вигнав хост кімнати.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="47"/>
+ <source>IP address is already in use. Please choose another.</source>
+ <translation>IP-адреса вже використовується. Будь ласка, виберіть іншу.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="49"/>
+ <source>You do not have enough permission to perform this action.</source>
+ <translation>У вас немає достатніх дозволів для виконання цієї дії.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="50"/>
+ <source>The user you are trying to kick/ban could not be found.
+They may have left the room.</source>
+ <translation>Користувача, якого ви намагаєтеся вигнати/заблокувати, не знайдено.
+Можливо, вони покинули кімнату.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="52"/>
+ <source>No valid network interface is selected.
+Please go to Configure -&gt; System -&gt; Network and make a selection.</source>
+ <translation>Не вибрано припустимий інтерфейс мережі.
+Будь ласка, перейдіть у Налаштування -&gt; Система -&gt; Мережа та зробіть вибір.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="68"/>
+ <source>Game already running</source>
+ <translation>Гру вже запущено</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="69"/>
+ <source>Joining a room when the game is already running is discouraged and can cause the room feature not to work correctly.
+Proceed anyway?</source>
+ <translation>Приєднуватися до кімнати, коли гру вже запущено, не рекомендується, це може призвести до неправильної роботи функції кімнати.
+Все одно продовжити?</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="75"/>
+ <source>Leave Room</source>
+ <translation>Залишити кімнату</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="76"/>
+ <source>You are about to close the room. Any network connections will be closed.</source>
+ <translation>Ви збираєтеся закрити кімнату. Усі мережеві підключення буде закрито.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="81"/>
+ <source>Disconnect</source>
+ <translation>Від&apos;єднатися</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="82"/>
+ <source>You are about to leave the room. Any network connections will be closed.</source>
+ <translation>Ви збираєтеся покинути кімнату. Усі мережеві підключення буде закрито.</translation>
+ </message>
+</context>
+<context>
+ <name>NetworkMessage::ErrorManager</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/message.cpp" line="63"/>
+ <source>Error</source>
+ <translation>Помилка</translation>
+ </message>
+</context>
+<context>
+ <name>OverlayDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="14"/>
+ <source>Dialog</source>
+ <translation>Діалог</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="134"/>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="353"/>
+ <source>Cancel</source>
+ <translation>Скасувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="152"/>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="371"/>
+ <source>OK</source>
+ <translation>ОК</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/util/overlay_dialog.ui" line="313"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:18pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:18pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+</context>
+<context>
+ <name>PlayerControlPreview</name>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player_widget.cpp" line="1575"/>
+ <source>START/PAUSE</source>
+ <translation>СТАРТ/ПАУЗА</translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby_p.h" line="236"/>
+ <source>%1 is not playing a game</source>
+ <translation>%1 не грає у гру</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/lobby_p.h" line="238"/>
+ <source>%1 is playing %2</source>
+ <translation>%1 грає в %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/multiplayer/chat_room.cpp" line="142"/>
+ <source>Not playing a game</source>
+ <translation>Не грає в гру</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="242"/>
+ <source>Installed SD Titles</source>
+ <translation>Встановлені SD ігри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="250"/>
+ <source>Installed NAND Titles</source>
+ <translation>Встановлені NAND ігри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="258"/>
+ <source>System Titles</source>
+ <translation>Системні ігри</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="301"/>
+ <source>Add New Game Directory</source>
+ <translation>Додати нову папку з іграми</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/game_list_p.h" line="324"/>
+ <source>Favorites</source>
+ <translation>Улюблені</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="21"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="30"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="41"/>
+ <source>Shift</source>
+ <translation>Shift</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="23"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="32"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="43"/>
+ <source>Ctrl</source>
+ <translation>Ctrl</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="25"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="34"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="45"/>
+ <source>Alt</source>
+ <translation>Alt</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="35"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="318"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="384"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="159"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="226"/>
+ <source>[not set]</source>
+ <translation>[не задано]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="47"/>
+ <source>Hat %1 %2</source>
+ <translation>Напр. %1 %2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="54"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="407"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="411"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="415"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="419"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="249"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="253"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="257"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="261"/>
+ <source>Axis %1%2</source>
+ <translation>Ось %1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="60"/>
+ <source>Button %1</source>
+ <translation>Кнопка %1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_touch_from_button.cpp" line="66"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="378"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="392"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="422"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="220"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="234"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="264"/>
+ <source>[unknown]</source>
+ <translation>[невідомо]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="45"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="56"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="124"/>
+ <source>Left</source>
+ <translation>Вліво</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="47"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="58"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="127"/>
+ <source>Right</source>
+ <translation>Вправо</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="49"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="60"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="133"/>
+ <source>Down</source>
+ <translation>Вниз</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="51"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="62"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="130"/>
+ <source>Up</source>
+ <translation>Вгору</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="53"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="64"/>
+ <source>Z</source>
+ <translation>Z</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="55"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="66"/>
+ <source>R</source>
+ <translation>R</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="57"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="68"/>
+ <source>L</source>
+ <translation>L</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="59"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="70"/>
+ <source>A</source>
+ <translation>A</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="61"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="72"/>
+ <source>B</source>
+ <translation>B</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="63"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="74"/>
+ <source>X</source>
+ <translation>X</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="65"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="76"/>
+ <source>Y</source>
+ <translation>Y</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="67"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="78"/>
+ <source>Start</source>
+ <translation>Start</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="69"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="80"/>
+ <source>L1</source>
+ <translation>L1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="71"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="82"/>
+ <source>L2</source>
+ <translation>L2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="73"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="84"/>
+ <source>L3</source>
+ <translation>L3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="75"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="86"/>
+ <source>R1</source>
+ <translation>R1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="77"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="88"/>
+ <source>R2</source>
+ <translation>R2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="79"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="90"/>
+ <source>R3</source>
+ <translation>R3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="81"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="92"/>
+ <source>Circle</source>
+ <translation>Кружечок</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="83"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="94"/>
+ <source>Cross</source>
+ <translation>Хрестик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="85"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="96"/>
+ <source>Square</source>
+ <translation>Квадратик</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="87"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="98"/>
+ <source>Triangle</source>
+ <translation>Трикутничок</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="89"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="100"/>
+ <source>Share</source>
+ <translation>Share</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="91"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="102"/>
+ <source>Options</source>
+ <translation>Options</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="93"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="118"/>
+ <source>[undefined]</source>
+ <translation>[невизначено]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="328"/>
+ <source>%1%2</source>
+ <translation>%1%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="332"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="174"/>
+ <source>[invalid]</source>
+ <translation>[неприпустимо]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="342"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="366"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="184"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="208"/>
+ <source>%1%2Hat %3</source>
+ <translation>%1%2Напр. %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="346"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="369"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="372"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="188"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="211"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="214"/>
+ <source>%1%2Axis %3</source>
+ <translation>%1%2Ось %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="352"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="194"/>
+ <source>%1%2Axis %3,%4,%5</source>
+ <translation>%1%2Ось %3,%4,%5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="356"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="198"/>
+ <source>%1%2Motion %3</source>
+ <translation>%1%2Рух %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="360"/>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="375"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="202"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="217"/>
+ <source>%1%2Button %3</source>
+ <translation>%1%2Кнопка %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_ringcon.cpp" line="402"/>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="244"/>
+ <source>[unused]</source>
+ <translation>[не використаний]</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="104"/>
+ <source>Home</source>
+ <translation>Home</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="106"/>
+ <source>Touch</source>
+ <translation>Сенсор</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="108"/>
+ <source>Wheel</source>
+ <comment>Indicates the mouse wheel</comment>
+ <translation>Коліщатко</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="110"/>
+ <source>Backward</source>
+ <translation>Назад</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="112"/>
+ <source>Forward</source>
+ <translation>Вперед</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="114"/>
+ <source>Task</source>
+ <translation>Задача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="116"/>
+ <source>Extra</source>
+ <translation>Додаткова</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/configuration/configure_input_player.cpp" line="170"/>
+ <source>%1%2%3</source>
+ <translation>%1%2%3</translation>
+ </message>
+</context>
+<context>
+ <name>QtControllerSelectorDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="14"/>
+ <source>Controller Applet</source>
+ <translation>Аплет контролера</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="129"/>
+ <source>Supported Controller Types:</source>
+ <translation>Підтримувані типи контролерів:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="282"/>
+ <source>Players:</source>
+ <translation>Гравці:</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="300"/>
+ <source>1 - 8</source>
+ <translation>1 - 8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="418"/>
+ <source>P4</source>
+ <translation>P4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="514"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="711"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="912"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1222"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1459"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1656"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1857"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2054"/>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="414"/>
+ <source>Pro Controller</source>
+ <translation>Контролер Pro</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="519"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="716"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="917"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1227"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1464"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1661"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1862"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2059"/>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="418"/>
+ <source>Dual Joycons</source>
+ <translation>Подвійні Joy-Con&apos;и</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="524"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="721"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="922"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1232"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1469"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1666"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1867"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2064"/>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="422"/>
+ <source>Left Joycon</source>
+ <translation>Лівий Joy-Con</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="529"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="726"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="927"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1237"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1474"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1671"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1872"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2069"/>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="426"/>
+ <source>Right Joycon</source>
+ <translation>Правий Joy-Con</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="538"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="735"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="941"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1246"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1483"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1680"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1881"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2078"/>
+ <source>Use Current Config</source>
+ <translation>Використовувати поточну конфігурацію</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="615"/>
+ <source>P2</source>
+ <translation>P2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="812"/>
+ <source>P1</source>
+ <translation>P1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="932"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2303"/>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="430"/>
+ <source>Handheld</source>
+ <translation>Портативний</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1126"/>
+ <source>P3</source>
+ <translation>P3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1363"/>
+ <source>P7</source>
+ <translation>P7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1560"/>
+ <source>P8</source>
+ <translation>P8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1757"/>
+ <source>P5</source>
+ <translation>P5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="1958"/>
+ <source>P6</source>
+ <translation>P6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2272"/>
+ <source>Console Mode</source>
+ <translation>Режим консолі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2293"/>
+ <source>Docked</source>
+ <translation>У док-станції</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2313"/>
+ <source>Vibration</source>
+ <translation>Вібрація</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2349"/>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2395"/>
+ <source>Configure</source>
+ <translation>Налаштувати</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2359"/>
+ <source>Motion</source>
+ <translation>Рух</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2405"/>
+ <source>Profiles</source>
+ <translation>Профілі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2432"/>
+ <source>Create</source>
+ <translation>Створити</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2467"/>
+ <source>Controllers</source>
+ <translation>Контролери</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2481"/>
+ <source>1</source>
+ <translation>1</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2508"/>
+ <source>2</source>
+ <translation>2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2518"/>
+ <source>4</source>
+ <translation>4</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2528"/>
+ <source>3</source>
+ <translation>3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2538"/>
+ <source>Connected</source>
+ <translation>З&apos;єднано</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2552"/>
+ <source>5</source>
+ <translation>5</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2569"/>
+ <source>7</source>
+ <translation>7</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2586"/>
+ <source>6</source>
+ <translation>6</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.ui" line="2596"/>
+ <source>8</source>
+ <translation>8</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="434"/>
+ <source>GameCube Controller</source>
+ <translation>Контролер GameCube</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="443"/>
+ <source>Poke Ball Plus</source>
+ <translation>Poke Ball Plus</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="447"/>
+ <source>NES Controller</source>
+ <translation>Контролер NES</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="451"/>
+ <source>SNES Controller</source>
+ <translation>Контролер SNES</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="455"/>
+ <source>N64 Controller</source>
+ <translation>Контролер N64</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_controller.cpp" line="459"/>
+ <source>Sega Genesis</source>
+ <translation>Sega Genesis</translation>
+ </message>
+</context>
+<context>
+ <name>QtErrorDisplay</name>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="20"/>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="33"/>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="48"/>
+ <source>Error Code: %1-%2 (0x%3)</source>
+ <translation>Код помилки: %1-%2 (0x%3)</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="24"/>
+ <source>An error has occurred.
+Please try again or contact the developer of the software.</source>
+ <translation>Сталася помилка.
+Будь ласка, спробуйте ще раз або зв&apos;яжіться з розробником ПЗ.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="37"/>
+ <source>An error occurred on %1 at %2.
+Please try again or contact the developer of the software.</source>
+ <translation>Сталася помилка на %1 у %2.
+Будь ласка, спробуйте ще раз або зв&apos;яжіться з розробником ПЗ.</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_error.cpp" line="52"/>
+ <source>An error has occurred.
+
+%1
+
+%2</source>
+ <translation>Сталася помилка.
+
+%1
+
+%2</translation>
+ </message>
+</context>
+<context>
+ <name>QtProfileSelectionDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="23"/>
+ <source>%1
+%2</source>
+ <comment>%1 is the profile username, %2 is the formatted UUID (e.g. 00112233-4455-6677-8899-AABBCCDDEEFF))</comment>
+ <translation>%1
+%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="53"/>
+ <source>Select a user:</source>
+ <translation>Оберить користувача</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="83"/>
+ <source>Users</source>
+ <translation>Користувачі</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_profile_select.cpp" line="123"/>
+ <source>Profile Selector</source>
+ <translation>Вибір профілю</translation>
+ </message>
+</context>
+<context>
+ <name>QtSoftwareKeyboardDialog</name>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="14"/>
+ <source>Software Keyboard</source>
+ <translation>Віртуальна клавіатура</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="199"/>
+ <source>Enter Text</source>
+ <translation>Введіть текст</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.ui" line="479"/>
+ <source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:26pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+ <translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
+&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
+p, li { white-space: pre-wrap; }
+&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;MS Shell Dlg 2&apos;; font-size:26pt; font-weight:400; font-style:normal;&quot;&gt;
+&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.cpp" line="403"/>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.cpp" line="413"/>
+ <source>OK</source>
+ <translation>ОК</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/applets/qt_software_keyboard.cpp" line="413"/>
+ <source>Cancel</source>
+ <translation>Скасувати</translation>
+ </message>
+</context>
+<context>
+ <name>SequenceDialog</name>
+ <message>
+ <location filename="../../src/yuzu/util/sequence_dialog/sequence_dialog.cpp" line="10"/>
+ <source>Enter a hotkey</source>
+ <translation>Введіть комбінацію</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeCallstack</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="147"/>
+ <source>Call stack</source>
+ <translation>Стек викликів</translation>
+ </message>
+</context>
+<context>
+ <name>WaitTreeMutexInfo</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="126"/>
+ <source>waiting for mutex 0x%1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="133"/>
+ <source>has waiters: %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="135"/>
+ <source>owner handle: 0x%1</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeObjectList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="228"/>
+ <source>waiting for all objects</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="229"/>
+ <source>waiting for one of the following objects</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeSynchronizationObject</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="185"/>
+ <source>[%1] %2 %3</source>
+ <translation>[%1] %2 %3</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="212"/>
+ <source>waited by no thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThread</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="250"/>
+ <source>runnable</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="252"/>
+ <source>paused</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="258"/>
+ <source>sleeping</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="261"/>
+ <source>waiting for IPC reply</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="264"/>
+ <source>waiting for objects</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="267"/>
+ <source>waiting for condition variable</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="270"/>
+ <source>waiting for address arbiter</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="273"/>
+ <source>waiting for suspend resume</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="276"/>
+ <source>waiting</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="281"/>
+ <source>initialized</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="284"/>
+ <source>terminated</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="287"/>
+ <source>unknown</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="292"/>
+ <source> PC = 0x%1 LR = 0x%2</source>
+ <translation> PC = 0x%1 LR = 0x%2</translation>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="342"/>
+ <source>ideal</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="345"/>
+ <source>core %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="349"/>
+ <source>processor = %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="351"/>
+ <source>ideal core = %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="353"/>
+ <source>affinity mask = %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="354"/>
+ <source>thread id = %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="355"/>
+ <source>priority = %1(current) / %2(normal)</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="359"/>
+ <source>last running ticks = %1</source>
+ <translation type="unfinished"/>
+ </message>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="367"/>
+ <source>not waiting for mutex</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeThreadList</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="391"/>
+ <source>waited by thread</source>
+ <translation type="unfinished"/>
+ </message>
+</context>
+<context>
+ <name>WaitTreeWidget</name>
+ <message>
+ <location filename="../../src/yuzu/debugger/wait_tree.cpp" line="465"/>
+ <source>&amp;Wait Tree</source>
+ <translation>[&amp;W] Дерево очікування</translation>
+ </message>
+</context>
+</TS> \ No newline at end of file
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index e80fd124e..7f0a6d069 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -7,15 +7,14 @@ include(DownloadExternals)
# xbyak
if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
- add_library(xbyak INTERFACE)
- file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include)
- file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/xbyak/xbyak DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include)
- target_include_directories(xbyak SYSTEM INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/xbyak/include)
- target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES)
+ add_subdirectory(xbyak)
endif()
# Dynarmic
-if (ARCHITECTURE_x86_64)
+if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
+ if (ARCHITECTURE_arm64)
+ set(DYNARMIC_FRONTENDS "A32")
+ endif()
set(DYNARMIC_NO_BUNDLED_FMT ON)
set(DYNARMIC_IGNORE_ASSERTS ON CACHE BOOL "" FORCE)
add_subdirectory(dynarmic)
diff --git a/externals/dynarmic b/externals/dynarmic
-Subproject 2d4602a6516c67d547000d4c80bcc5f74976abd
+Subproject 424fdb5c5026ec5bdd7553271190397f63fb503
diff --git a/externals/xbyak b/externals/xbyak
-Subproject c306b8e5786eeeb87b8925a8af5c3bf057ff5a9
+Subproject 348e3e548ebac06d243e5881caec8440e249f65
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 0a1f3bf18..8e3a8f5a8 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -217,7 +217,7 @@ else()
endif()
target_link_libraries(audio_core PUBLIC common core)
-if (ARCHITECTURE_x86_64)
+if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
target_link_libraries(audio_core PRIVATE dynarmic)
endif()
diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp
index 6b7e6715c..4324cafd8 100644
--- a/src/audio_core/in/audio_in_system.cpp
+++ b/src/audio_core/in/audio_in_system.cpp
@@ -56,7 +56,7 @@ Result System::IsConfigValid(const std::string_view device_name,
return ResultSuccess;
}
-Result System::Initialize(std::string& device_name, const AudioInParameter& in_params,
+Result System::Initialize(std::string device_name, const AudioInParameter& in_params,
const u32 handle_, const u64 applet_resource_user_id_) {
auto result{IsConfigValid(device_name, in_params)};
if (result.IsError()) {
diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h
index b9dc0e60f..1c5154638 100644
--- a/src/audio_core/in/audio_in_system.h
+++ b/src/audio_core/in/audio_in_system.h
@@ -97,7 +97,7 @@ public:
* @param applet_resource_user_id - Unused.
* @return Result code.
*/
- Result Initialize(std::string& device_name, const AudioInParameter& in_params, u32 handle,
+ Result Initialize(std::string device_name, const AudioInParameter& in_params, u32 handle,
u64 applet_resource_user_id);
/**
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp
index 48a801923..a66208ed9 100644
--- a/src/audio_core/out/audio_out_system.cpp
+++ b/src/audio_core/out/audio_out_system.cpp
@@ -49,8 +49,8 @@ Result System::IsConfigValid(std::string_view device_name,
return Service::Audio::ERR_INVALID_CHANNEL_COUNT;
}
-Result System::Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle_,
- u64& applet_resource_user_id_) {
+Result System::Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle_,
+ u64 applet_resource_user_id_) {
auto result = IsConfigValid(device_name, in_params);
if (result.IsError()) {
return result;
diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h
index 0817b2f37..b95cb91be 100644
--- a/src/audio_core/out/audio_out_system.h
+++ b/src/audio_core/out/audio_out_system.h
@@ -88,8 +88,8 @@ public:
* @param applet_resource_user_id - Unused.
* @return Result code.
*/
- Result Initialize(std::string& device_name, const AudioOutParameter& in_params, u32 handle,
- u64& applet_resource_user_id);
+ Result Initialize(std::string device_name, const AudioOutParameter& in_params, u32 handle,
+ u64 applet_resource_user_id);
/**
* Start this system.
diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h
index e1e2a90fc..0dad9338a 100644
--- a/src/common/common_funcs.h
+++ b/src/common/common_funcs.h
@@ -31,8 +31,10 @@
#ifndef _MSC_VER
-#ifdef ARCHITECTURE_x86_64
+#if defined(ARCHITECTURE_x86_64)
#define Crash() __asm__ __volatile__("int $3")
+#elif defined(ARCHITECTURE_arm64)
+#define Crash() __asm__ __volatile__("brk #0")
#else
#define Crash() exit(1)
#endif
diff --git a/src/common/concepts.h b/src/common/concepts.h
index e8ce30dfe..a9acff3e7 100644
--- a/src/common/concepts.h
+++ b/src/common/concepts.h
@@ -3,24 +3,14 @@
#pragma once
+#include <iterator>
#include <type_traits>
namespace Common {
-// Check if type is like an STL container
+// Check if type satisfies the ContiguousContainer named requirement.
template <typename T>
-concept IsSTLContainer = requires(T t) {
- typename T::value_type;
- typename T::iterator;
- typename T::const_iterator;
- // TODO(ogniK): Replace below is std::same_as<void> when MSVC supports it.
- t.begin();
- t.end();
- t.cbegin();
- t.cend();
- t.data();
- t.size();
-};
+concept IsContiguousContainer = std::contiguous_iterator<typename T::iterator>;
// TODO: Replace with std::derived_from when the <concepts> header
// is available on all supported platforms.
diff --git a/src/common/fs/file.h b/src/common/fs/file.h
index 69b53384c..167c4d826 100644
--- a/src/common/fs/file.h
+++ b/src/common/fs/file.h
@@ -209,8 +209,8 @@ public:
/**
* Helper function which deduces the value type of a contiguous STL container used in ReadSpan.
- * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
- * ReadObject and T must be a trivially copyable object.
+ * If T is not a contiguous container as defined by the concept IsContiguousContainer, this
+ * calls ReadObject and T must be a trivially copyable object.
*
* See ReadSpan for more details if T is a contiguous container.
* See ReadObject for more details if T is a trivially copyable object.
@@ -223,7 +223,7 @@ public:
*/
template <typename T>
[[nodiscard]] size_t Read(T& data) const {
- if constexpr (IsSTLContainer<T>) {
+ if constexpr (IsContiguousContainer<T>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
@@ -235,8 +235,8 @@ public:
/**
* Helper function which deduces the value type of a contiguous STL container used in WriteSpan.
- * If T is not a contiguous STL container as defined by the concept IsSTLContainer, this calls
- * WriteObject and T must be a trivially copyable object.
+ * If T is not a contiguous STL container as defined by the concept IsContiguousContainer, this
+ * calls WriteObject and T must be a trivially copyable object.
*
* See WriteSpan for more details if T is a contiguous container.
* See WriteObject for more details if T is a trivially copyable object.
@@ -249,7 +249,7 @@ public:
*/
template <typename T>
[[nodiscard]] size_t Write(const T& data) const {
- if constexpr (IsSTLContainer<T>) {
+ if constexpr (IsContiguousContainer<T>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Data type must be trivially copyable.");
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
index 7f9659612..909f6cf3f 100644
--- a/src/common/host_memory.cpp
+++ b/src/common/host_memory.cpp
@@ -359,6 +359,12 @@ public:
}
});
+ long page_size = sysconf(_SC_PAGESIZE);
+ if (page_size != 0x1000) {
+ LOG_CRITICAL(HW_Memory, "page size {:#x} is incompatible with 4K paging", page_size);
+ throw std::bad_alloc{};
+ }
+
// Backing memory initialization
#if defined(__FreeBSD__) && __FreeBSD__ < 13
// XXX Drop after FreeBSD 12.* reaches EOL on 2024-06-30
diff --git a/src/common/input.h b/src/common/input.h
index b533f3844..cb30b7254 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -100,7 +100,6 @@ enum class CameraError {
enum class VibrationAmplificationType {
Linear,
Exponential,
- Test,
};
// Analog properties for calibration
@@ -325,6 +324,10 @@ public:
return VibrationError::NotSupported;
}
+ virtual bool IsVibrationEnabled() {
+ return false;
+ }
+
virtual PollingError SetPollingMode([[maybe_unused]] PollingMode polling_mode) {
return PollingError::NotSupported;
}
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 0a560ebb7..8173462cb 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -151,6 +151,7 @@ void UpdateRescalingInfo() {
ASSERT(false);
info.up_scale = 1;
info.down_shift = 0;
+ break;
}
info.up_factor = static_cast<f32>(info.up_scale) / (1U << info.down_shift);
info.down_factor = static_cast<f32>(1U << info.down_shift) / info.up_scale;
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 113e663b5..f67f1ce92 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -190,11 +190,13 @@ add_library(core STATIC
hle/kernel/k_code_memory.h
hle/kernel/k_condition_variable.cpp
hle/kernel/k_condition_variable.h
+ hle/kernel/k_debug.h
hle/kernel/k_dynamic_page_manager.h
hle/kernel/k_dynamic_resource_manager.h
hle/kernel/k_dynamic_slab_heap.h
hle/kernel/k_event.cpp
hle/kernel/k_event.h
+ hle/kernel/k_event_info.h
hle/kernel/k_handle_table.cpp
hle/kernel/k_handle_table.h
hle/kernel/k_interrupt_manager.cpp
@@ -222,6 +224,8 @@ add_library(core STATIC
hle/kernel/k_page_group.h
hle/kernel/k_page_table.cpp
hle/kernel/k_page_table.h
+ hle/kernel/k_page_table_manager.h
+ hle/kernel/k_page_table_slab_heap.h
hle/kernel/k_port.cpp
hle/kernel/k_port.h
hle/kernel/k_priority_queue.h
@@ -254,6 +258,8 @@ add_library(core STATIC
hle/kernel/k_synchronization_object.cpp
hle/kernel/k_synchronization_object.h
hle/kernel/k_system_control.h
+ hle/kernel/k_system_resource.cpp
+ hle/kernel/k_system_resource.h
hle/kernel/k_thread.cpp
hle/kernel/k_thread.h
hle/kernel/k_thread_local_page.cpp
@@ -491,10 +497,6 @@ add_library(core STATIC
hle/service/hid/irsensor/processor_base.h
hle/service/hid/irsensor/tera_plugin_processor.cpp
hle/service/hid/irsensor/tera_plugin_processor.h
- hle/service/jit/jit_context.cpp
- hle/service/jit/jit_context.h
- hle/service/jit/jit.cpp
- hle/service/jit/jit.h
hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h
hle/service/ldn/lan_discovery.cpp
@@ -799,14 +801,18 @@ if (ENABLE_WEB_SERVICE)
target_link_libraries(core PRIVATE web_service)
endif()
-if (ARCHITECTURE_x86_64)
+if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
target_sources(core PRIVATE
- arm/dynarmic/arm_dynarmic_32.cpp
- arm/dynarmic/arm_dynarmic_32.h
arm/dynarmic/arm_dynarmic_64.cpp
arm/dynarmic/arm_dynarmic_64.h
+ arm/dynarmic/arm_dynarmic_32.cpp
+ arm/dynarmic/arm_dynarmic_32.h
arm/dynarmic/arm_dynarmic_cp15.cpp
arm/dynarmic/arm_dynarmic_cp15.h
+ hle/service/jit/jit_context.cpp
+ hle/service/jit/jit_context.h
+ hle/service/jit/jit.cpp
+ hle/service/jit/jit.h
)
target_link_libraries(core PRIVATE dynarmic)
endif()
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
index d1e70f19d..227e06ea1 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp
@@ -301,6 +301,11 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
}
}
+#ifdef ARCHITECTURE_arm64
+ // TODO: remove when fixed in dynarmic
+ config.optimizations &= ~Dynarmic::OptimizationFlag::BlockLinking;
+#endif
+
return std::make_unique<Dynarmic::A32::Jit>(config);
}
@@ -450,7 +455,7 @@ std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_32::GetBacktrace(Core::S
// Frame records are two words long:
// fp+0 : pointer to previous frame record
// fp+4 : value of lr for frame
- while (true) {
+ for (size_t i = 0; i < 256; i++) {
out.push_back({"", 0, lr, 0, ""});
if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 8)) {
break;
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index 22b5d5656..afb7fb3a0 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -517,7 +517,7 @@ std::vector<ARM_Interface::BacktraceEntry> ARM_Dynarmic_64::GetBacktrace(Core::S
// Frame records are two words long:
// fp+0 : pointer to previous frame record
// fp+8 : value of lr for frame
- while (true) {
+ for (size_t i = 0; i < 256; i++) {
out.push_back({"", 0, lr, 0, ""});
if (!fp || (fp % 4 != 0) || !memory.IsValidVirtualAddressRange(fp, 16)) {
break;
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
index 200efe4db..5a4eba3eb 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
@@ -52,12 +52,16 @@ CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1
case 4:
// CP15_DATA_SYNC_BARRIER
return Callback{
- [](Dynarmic::A32::Jit*, void*, std::uint32_t, std::uint32_t) -> std::uint64_t {
-#ifdef _MSC_VER
+ [](void*, std::uint32_t, std::uint32_t) -> std::uint64_t {
+#if defined(_MSC_VER) && defined(ARCHITECTURE_x86_64)
_mm_mfence();
_mm_lfence();
-#else
+#elif defined(ARCHITECTURE_x86_64)
asm volatile("mfence\n\tlfence\n\t" : : : "memory");
+#elif defined(ARCHITECTURE_arm64)
+ asm volatile("dsb sy\n\t" : : : "memory");
+#else
+#error Unsupported architecture
#endif
return 0;
},
@@ -66,11 +70,15 @@ CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1
case 5:
// CP15_DATA_MEMORY_BARRIER
return Callback{
- [](Dynarmic::A32::Jit*, void*, std::uint32_t, std::uint32_t) -> std::uint64_t {
-#ifdef _MSC_VER
+ [](void*, std::uint32_t, std::uint32_t) -> std::uint64_t {
+#if defined(_MSC_VER) && defined(ARCHITECTURE_x86_64)
_mm_mfence();
-#else
+#elif defined(ARCHITECTURE_x86_64)
asm volatile("mfence\n\t" : : : "memory");
+#elif defined(ARCHITECTURE_arm64)
+ asm volatile("dmb sy\n\t" : : : "memory");
+#else
+#error Unsupported architecture
#endif
return 0;
},
@@ -115,7 +123,7 @@ CallbackOrAccessOneWord DynarmicCP15::CompileGetOneWord(bool two, unsigned opc1,
CallbackOrAccessTwoWords DynarmicCP15::CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) {
if (!two && opc == 0 && CRm == CoprocReg::C14) {
// CNTPCT
- const auto callback = [](Dynarmic::A32::Jit*, void* arg, u32, u32) -> u64 {
+ const auto callback = [](void* arg, u32, u32) -> u64 {
const auto& parent_arg = *static_cast<ARM_Dynarmic_32*>(arg);
return parent_arg.system.CoreTiming().GetClockTicks();
};
diff --git a/src/core/arm/exclusive_monitor.cpp b/src/core/arm/exclusive_monitor.cpp
index 2db0b035d..20550faeb 100644
--- a/src/core/arm/exclusive_monitor.cpp
+++ b/src/core/arm/exclusive_monitor.cpp
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#ifdef ARCHITECTURE_x86_64
+#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
#endif
#include "core/arm/exclusive_monitor.h"
@@ -13,7 +13,7 @@ ExclusiveMonitor::~ExclusiveMonitor() = default;
std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,
std::size_t num_cores) {
-#ifdef ARCHITECTURE_x86_64
+#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
return std::make_unique<Core::DynarmicExclusiveMonitor>(memory, num_cores);
#else
// TODO(merry): Passthrough exclusive monitor
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 40a610435..d8934be52 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -137,6 +137,7 @@ struct System::Impl {
device_memory = std::make_unique<Core::DeviceMemory>();
is_multicore = Settings::values.use_multi_core.GetValue();
+ extended_memory_layout = Settings::values.use_extended_memory_layout.GetValue();
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
@@ -166,13 +167,18 @@ struct System::Impl {
}
void ReinitializeIfNecessary(System& system) {
- if (is_multicore == Settings::values.use_multi_core.GetValue()) {
+ const bool must_reinitialize =
+ is_multicore != Settings::values.use_multi_core.GetValue() ||
+ extended_memory_layout != Settings::values.use_extended_memory_layout.GetValue();
+
+ if (!must_reinitialize) {
return;
}
LOG_DEBUG(Kernel, "Re-initializing");
is_multicore = Settings::values.use_multi_core.GetValue();
+ extended_memory_layout = Settings::values.use_extended_memory_layout.GetValue();
Initialize(system);
}
@@ -521,6 +527,7 @@ struct System::Impl {
bool is_multicore{};
bool is_async_gpu{};
+ bool extended_memory_layout{};
ExecuteProgramCallback execute_program_callback;
ExitCallback exit_callback;
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index be25da2f6..50f44f598 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/settings.h"
#include "common/string_util.h"
#include "common/swap.h"
#include "core/file_sys/control_metadata.h"
@@ -37,6 +38,27 @@ std::string LanguageEntry::GetDeveloperName() const {
developer_name.size());
}
+constexpr std::array<Language, 18> language_to_codes = {{
+ Language::Japanese,
+ Language::AmericanEnglish,
+ Language::French,
+ Language::German,
+ Language::Italian,
+ Language::Spanish,
+ Language::Chinese,
+ Language::Korean,
+ Language::Dutch,
+ Language::Portuguese,
+ Language::Russian,
+ Language::Taiwanese,
+ Language::BritishEnglish,
+ Language::CanadianFrench,
+ Language::LatinAmericanSpanish,
+ Language::Chinese,
+ Language::Taiwanese,
+ Language::BrazilianPortuguese,
+}};
+
NACP::NACP() = default;
NACP::NACP(VirtualFile file) {
@@ -45,9 +67,13 @@ NACP::NACP(VirtualFile file) {
NACP::~NACP() = default;
-const LanguageEntry& NACP::GetLanguageEntry(Language language) const {
- if (language != Language::Default) {
- return raw.language_entries.at(static_cast<u8>(language));
+const LanguageEntry& NACP::GetLanguageEntry() const {
+ Language language = language_to_codes[Settings::values.language_index.GetValue()];
+
+ {
+ const auto& language_entry = raw.language_entries.at(static_cast<u8>(language));
+ if (!language_entry.GetApplicationName().empty())
+ return language_entry;
}
for (const auto& language_entry : raw.language_entries) {
@@ -55,16 +81,15 @@ const LanguageEntry& NACP::GetLanguageEntry(Language language) const {
return language_entry;
}
- // Fallback to English
- return GetLanguageEntry(Language::AmericanEnglish);
+ return raw.language_entries.at(static_cast<u8>(Language::AmericanEnglish));
}
-std::string NACP::GetApplicationName(Language language) const {
- return GetLanguageEntry(language).GetApplicationName();
+std::string NACP::GetApplicationName() const {
+ return GetLanguageEntry().GetApplicationName();
}
-std::string NACP::GetDeveloperName(Language language) const {
- return GetLanguageEntry(language).GetDeveloperName();
+std::string NACP::GetDeveloperName() const {
+ return GetLanguageEntry().GetDeveloperName();
}
u64 NACP::GetTitleId() const {
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 75295519c..6a81873b1 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -101,9 +101,9 @@ public:
explicit NACP(VirtualFile file);
~NACP();
- const LanguageEntry& GetLanguageEntry(Language language = Language::Default) const;
- std::string GetApplicationName(Language language = Language::Default) const;
- std::string GetDeveloperName(Language language = Language::Default) const;
+ const LanguageEntry& GetLanguageEntry() const;
+ std::string GetApplicationName() const;
+ std::string GetDeveloperName() const;
u64 GetTitleId() const;
u64 GetDLCBaseTitleId() const;
std::string GetVersionString() const;
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 57eff72fe..ec1364452 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -970,14 +970,7 @@ bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue v
Common::Input::VibrationError::None;
}
-bool EmulatedController::TestVibration(std::size_t device_index) {
- if (device_index >= output_devices.size()) {
- return false;
- }
- if (!output_devices[device_index]) {
- return false;
- }
-
+bool EmulatedController::IsVibrationEnabled(std::size_t device_index) {
const auto player_index = NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index];
@@ -985,31 +978,15 @@ bool EmulatedController::TestVibration(std::size_t device_index) {
return false;
}
- const Common::Input::VibrationStatus test_vibration = {
- .low_amplitude = 0.001f,
- .low_frequency = DEFAULT_VIBRATION_VALUE.low_frequency,
- .high_amplitude = 0.001f,
- .high_frequency = DEFAULT_VIBRATION_VALUE.high_frequency,
- .type = Common::Input::VibrationAmplificationType::Test,
- };
-
- const Common::Input::VibrationStatus zero_vibration = {
- .low_amplitude = DEFAULT_VIBRATION_VALUE.low_amplitude,
- .low_frequency = DEFAULT_VIBRATION_VALUE.low_frequency,
- .high_amplitude = DEFAULT_VIBRATION_VALUE.high_amplitude,
- .high_frequency = DEFAULT_VIBRATION_VALUE.high_frequency,
- .type = Common::Input::VibrationAmplificationType::Test,
- };
-
- // Send a slight vibration to test for rumble support
- output_devices[device_index]->SetVibration(test_vibration);
+ if (device_index >= output_devices.size()) {
+ return false;
+ }
- // Wait for about 15ms to ensure the controller is ready for the stop command
- std::this_thread::sleep_for(std::chrono::milliseconds(15));
+ if (!output_devices[device_index]) {
+ return false;
+ }
- // Stop any vibration and return the result
- return output_devices[device_index]->SetVibration(zero_vibration) ==
- Common::Input::VibrationError::None;
+ return output_devices[device_index]->IsVibrationEnabled();
}
bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) {
@@ -1048,6 +1025,7 @@ bool EmulatedController::HasNfc() const {
case NpadStyleIndex::JoyconRight:
case NpadStyleIndex::JoyconDual:
case NpadStyleIndex::ProController:
+ case NpadStyleIndex::Handheld:
break;
default:
return false;
@@ -1234,12 +1212,6 @@ bool EmulatedController::IsConnected(bool get_temporary_value) const {
return is_connected;
}
-bool EmulatedController::IsVibrationEnabled() const {
- const auto player_index = NpadIdTypeToIndex(npad_id_type);
- const auto& player = Settings::values.players.GetValue()[player_index];
- return player.vibration_enabled;
-}
-
NpadIdType EmulatedController::GetNpadIdType() const {
std::scoped_lock lock{mutex};
return npad_id_type;
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 319226bf8..d004ca56a 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -206,9 +206,6 @@ public:
*/
bool IsConnected(bool get_temporary_value = false) const;
- /// Returns true if vibration is enabled
- bool IsVibrationEnabled() const;
-
/// Removes all callbacks created from input devices
void UnloadInput();
@@ -339,7 +336,7 @@ public:
* Sends a small vibration to the output device
* @return true if SetVibration was successfull
*/
- bool TestVibration(std::size_t device_index);
+ bool IsVibrationEnabled(std::size_t device_index);
/**
* Sets the desired data to be polled from a controller
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 18fde8bd6..a86bec252 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -86,13 +86,13 @@ public:
u32 num_domain_objects{};
const bool always_move_handles{
(static_cast<u32>(flags) & static_cast<u32>(Flags::AlwaysMoveHandles)) != 0};
- if (!ctx.Session()->GetSessionRequestManager()->IsDomain() || always_move_handles) {
+ if (!ctx.GetManager()->IsDomain() || always_move_handles) {
num_handles_to_move = num_objects_to_move;
} else {
num_domain_objects = num_objects_to_move;
}
- if (ctx.Session()->GetSessionRequestManager()->IsDomain()) {
+ if (ctx.GetManager()->IsDomain()) {
raw_data_size +=
static_cast<u32>(sizeof(DomainMessageHeader) / sizeof(u32) + num_domain_objects);
ctx.write_size += num_domain_objects;
@@ -125,8 +125,7 @@ public:
if (!ctx.IsTipc()) {
AlignWithPadding();
- if (ctx.Session()->GetSessionRequestManager()->IsDomain() &&
- ctx.HasDomainMessageHeader()) {
+ if (ctx.GetManager()->IsDomain() && ctx.HasDomainMessageHeader()) {
IPC::DomainMessageHeader domain_header{};
domain_header.num_objects = num_domain_objects;
PushRaw(domain_header);
@@ -146,18 +145,18 @@ public:
template <class T>
void PushIpcInterface(std::shared_ptr<T> iface) {
- if (context->Session()->GetSessionRequestManager()->IsDomain()) {
+ if (context->GetManager()->IsDomain()) {
context->AddDomainObject(std::move(iface));
} else {
kernel.CurrentProcess()->GetResourceLimit()->Reserve(
- Kernel::LimitableResource::Sessions, 1);
+ Kernel::LimitableResource::SessionCountMax, 1);
auto* session = Kernel::KSession::Create(kernel);
- session->Initialize(nullptr, iface->GetServiceName(),
- std::make_shared<Kernel::SessionRequestManager>(kernel));
+ session->Initialize(nullptr, iface->GetServiceName());
+ iface->RegisterSession(&session->GetServerSession(),
+ std::make_shared<Kernel::SessionRequestManager>(kernel));
context->AddMoveObject(&session->GetClientSession());
- iface->ClientConnected(&session->GetServerSession());
}
}
@@ -387,7 +386,7 @@ public:
template <class T>
std::weak_ptr<T> PopIpcInterface() {
- ASSERT(context->Session()->GetSessionRequestManager()->IsDomain());
+ ASSERT(context->GetManager()->IsDomain());
ASSERT(context->GetDomainMessageHeader().input_object_count > 0);
return context->GetDomainHandler<T>(Pop<u32>() - 1);
}
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
index fe375769e..4b717d091 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.h
@@ -9,6 +9,10 @@ namespace Kernel::Board::Nintendo::Nx {
class KSystemControl {
public:
+ // This can be overridden as needed.
+ static constexpr size_t SecureAppletMemorySize = 4 * 1024 * 1024; // 4_MB
+
+public:
class Init {
public:
// Initialization.
diff --git a/src/core/hle/kernel/global_scheduler_context.cpp b/src/core/hle/kernel/global_scheduler_context.cpp
index 65576b8c4..fd911a3a5 100644
--- a/src/core/hle/kernel/global_scheduler_context.cpp
+++ b/src/core/hle/kernel/global_scheduler_context.cpp
@@ -49,4 +49,26 @@ bool GlobalSchedulerContext::IsLocked() const {
return scheduler_lock.IsLockedByCurrentThread();
}
+void GlobalSchedulerContext::RegisterDummyThreadForWakeup(KThread* thread) {
+ ASSERT(IsLocked());
+
+ woken_dummy_threads.insert(thread);
+}
+
+void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) {
+ ASSERT(IsLocked());
+
+ woken_dummy_threads.erase(thread);
+}
+
+void GlobalSchedulerContext::WakeupWaitingDummyThreads() {
+ ASSERT(IsLocked());
+
+ for (auto* thread : woken_dummy_threads) {
+ thread->DummyThreadEndWait();
+ }
+
+ woken_dummy_threads.clear();
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/global_scheduler_context.h b/src/core/hle/kernel/global_scheduler_context.h
index 67bb9852d..220ed6192 100644
--- a/src/core/hle/kernel/global_scheduler_context.h
+++ b/src/core/hle/kernel/global_scheduler_context.h
@@ -4,6 +4,7 @@
#pragma once
#include <atomic>
+#include <set>
#include <vector>
#include "common/common_types.h"
@@ -58,6 +59,10 @@ public:
/// Returns true if the global scheduler lock is acquired
bool IsLocked() const;
+ void UnregisterDummyThreadForWakeup(KThread* thread);
+ void RegisterDummyThreadForWakeup(KThread* thread);
+ void WakeupWaitingDummyThreads();
+
[[nodiscard]] LockType& SchedulerLock() {
return scheduler_lock;
}
@@ -76,6 +81,9 @@ private:
KSchedulerPriorityQueue priority_queue;
LockType scheduler_lock;
+ /// Lists dummy threads pending wakeup on lock release
+ std::set<KThread*> woken_dummy_threads;
+
/// Lists all thread ids that aren't deleted/etc.
std::vector<KThread*> thread_list;
std::mutex global_list_guard;
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index e4f43a053..06010b8d1 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -16,6 +16,7 @@
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/k_server_port.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
@@ -26,18 +27,28 @@ namespace Kernel {
SessionRequestHandler::SessionRequestHandler(KernelCore& kernel_, const char* service_name_,
ServiceThreadType thread_type)
- : kernel{kernel_} {
- if (thread_type == ServiceThreadType::CreateNew) {
- service_thread = kernel.CreateServiceThread(service_name_);
- } else {
- service_thread = kernel.GetDefaultServiceThread();
- }
-}
+ : kernel{kernel_}, service_thread{thread_type == ServiceThreadType::CreateNew
+ ? kernel.CreateServiceThread(service_name_)
+ : kernel.GetDefaultServiceThread()} {}
SessionRequestHandler::~SessionRequestHandler() {
kernel.ReleaseServiceThread(service_thread);
}
+void SessionRequestHandler::AcceptSession(KServerPort* server_port) {
+ auto* server_session = server_port->AcceptSession();
+ ASSERT(server_session != nullptr);
+
+ RegisterSession(server_session, std::make_shared<SessionRequestManager>(kernel));
+}
+
+void SessionRequestHandler::RegisterSession(KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager> manager) {
+ manager->SetSessionHandler(shared_from_this());
+ service_thread.RegisterServerSession(server_session, manager);
+ server_session->Close();
+}
+
SessionRequestManager::SessionRequestManager(KernelCore& kernel_) : kernel{kernel_} {}
SessionRequestManager::~SessionRequestManager() = default;
@@ -92,7 +103,7 @@ Result SessionRequestManager::HandleDomainSyncRequest(KServerSession* server_ses
}
// Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
- context.SetSessionRequestManager(server_session->GetSessionRequestManager());
+ ASSERT(context.GetManager().get() == this);
// If there is a DomainMessageHeader, then this is CommandType "Request"
const auto& domain_message_header = context.GetDomainMessageHeader();
@@ -130,31 +141,6 @@ Result SessionRequestManager::HandleDomainSyncRequest(KServerSession* server_ses
return ResultSuccess;
}
-Result SessionRequestManager::QueueSyncRequest(KSession* parent,
- std::shared_ptr<HLERequestContext>&& context) {
- // Ensure we have a session request handler
- if (this->HasSessionRequestHandler(*context)) {
- if (auto strong_ptr = this->GetServiceThread().lock()) {
- strong_ptr->QueueSyncRequest(*parent, std::move(context));
- } else {
- ASSERT_MSG(false, "strong_ptr is nullptr!");
- }
- } else {
- ASSERT_MSG(false, "handler is invalid!");
- }
-
- return ResultSuccess;
-}
-
-void SessionRequestHandler::ClientConnected(KServerSession* session) {
- session->GetSessionRequestManager()->SetSessionHandler(shared_from_this());
-
- // Ensure our server session is tracked globally.
- kernel.RegisterServerObject(session);
-}
-
-void SessionRequestHandler::ClientDisconnected(KServerSession* session) {}
-
HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_,
KServerSession* server_session_, KThread* thread_)
: server_session(server_session_), thread(thread_), kernel{kernel_}, memory{memory_} {
@@ -214,7 +200,7 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
// Padding to align to 16 bytes
rp.AlignWithPadding();
- if (Session()->GetSessionRequestManager()->IsDomain() &&
+ if (GetManager()->IsDomain() &&
((command_header->type == IPC::CommandType::Request ||
command_header->type == IPC::CommandType::RequestWithContext) ||
!incoming)) {
@@ -223,7 +209,7 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
if (incoming || domain_message_header) {
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
} else {
- if (Session()->GetSessionRequestManager()->IsDomain()) {
+ if (GetManager()->IsDomain()) {
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
}
}
@@ -316,12 +302,11 @@ Result HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_threa
// Write the domain objects to the command buffer, these go after the raw untranslated data.
// TODO(Subv): This completely ignores C buffers.
- if (server_session->GetSessionRequestManager()->IsDomain()) {
+ if (GetManager()->IsDomain()) {
current_offset = domain_offset - static_cast<u32>(outgoing_domain_objects.size());
for (auto& object : outgoing_domain_objects) {
- server_session->GetSessionRequestManager()->AppendDomainHandler(std::move(object));
- cmd_buf[current_offset++] = static_cast<u32_le>(
- server_session->GetSessionRequestManager()->DomainHandlerCount());
+ GetManager()->AppendDomainHandler(std::move(object));
+ cmd_buf[current_offset++] = static_cast<u32_le>(GetManager()->DomainHandlerCount());
}
}
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index a0522bca0..d87be72d6 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -45,11 +45,13 @@ class KAutoObject;
class KernelCore;
class KEvent;
class KHandleTable;
+class KServerPort;
class KProcess;
class KServerSession;
class KThread;
class KReadableEvent;
class KSession;
+class SessionRequestManager;
class ServiceThread;
enum class ThreadWakeupReason;
@@ -76,27 +78,17 @@ public:
virtual Result HandleSyncRequest(Kernel::KServerSession& session,
Kernel::HLERequestContext& context) = 0;
- /**
- * Signals that a client has just connected to this HLE handler and keeps the
- * associated ServerSession alive for the duration of the connection.
- * @param server_session Owning pointer to the ServerSession associated with the connection.
- */
- void ClientConnected(KServerSession* session);
+ void AcceptSession(KServerPort* server_port);
+ void RegisterSession(KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager> manager);
- /**
- * Signals that a client has just disconnected from this HLE handler and releases the
- * associated ServerSession.
- * @param server_session ServerSession associated with the connection.
- */
- void ClientDisconnected(KServerSession* session);
-
- std::weak_ptr<ServiceThread> GetServiceThread() const {
+ ServiceThread& GetServiceThread() const {
return service_thread;
}
protected:
KernelCore& kernel;
- std::weak_ptr<ServiceThread> service_thread;
+ ServiceThread& service_thread;
};
using SessionRequestHandlerWeakPtr = std::weak_ptr<SessionRequestHandler>;
@@ -162,7 +154,7 @@ public:
session_handler = std::move(handler);
}
- std::weak_ptr<ServiceThread> GetServiceThread() const {
+ ServiceThread& GetServiceThread() const {
return session_handler->GetServiceThread();
}
@@ -170,7 +162,6 @@ public:
Result HandleDomainSyncRequest(KServerSession* server_session, HLERequestContext& context);
Result CompleteSyncRequest(KServerSession* server_session, HLERequestContext& context);
- Result QueueSyncRequest(KSession* parent, std::shared_ptr<HLERequestContext>&& context);
private:
bool convert_to_domain{};
@@ -304,7 +295,7 @@ public:
*/
template <typename T, typename = std::enable_if_t<!std::is_pointer_v<T>>>
std::size_t WriteBuffer(const T& data, std::size_t buffer_index = 0) const {
- if constexpr (Common::IsSTLContainer<T>) {
+ if constexpr (Common::IsContiguousContainer<T>) {
using ContiguousType = typename T::value_type;
static_assert(std::is_trivially_copyable_v<ContiguousType>,
"Container to WriteBuffer must contain trivially copyable objects");
@@ -350,11 +341,11 @@ public:
template <typename T>
std::shared_ptr<T> GetDomainHandler(std::size_t index) const {
- return std::static_pointer_cast<T>(manager.lock()->DomainHandler(index).lock());
+ return std::static_pointer_cast<T>(GetManager()->DomainHandler(index).lock());
}
void SetSessionRequestManager(std::weak_ptr<SessionRequestManager> manager_) {
- manager = std::move(manager_);
+ manager = manager_;
}
std::string Description() const;
@@ -363,6 +354,10 @@ public:
return *thread;
}
+ std::shared_ptr<SessionRequestManager> GetManager() const {
+ return manager.lock();
+ }
+
private:
friend class IPC::ResponseBuilder;
@@ -396,7 +391,7 @@ private:
u32 handles_offset{};
u32 domain_offset{};
- std::weak_ptr<SessionRequestManager> manager;
+ std::weak_ptr<SessionRequestManager> manager{};
KernelCore& kernel;
Core::Memory::Memory& memory;
diff --git a/src/core/hle/kernel/init/init_slab_setup.cpp b/src/core/hle/kernel/init/init_slab_setup.cpp
index 477e4e407..bda098511 100644
--- a/src/core/hle/kernel/init/init_slab_setup.cpp
+++ b/src/core/hle/kernel/init/init_slab_setup.cpp
@@ -10,7 +10,9 @@
#include "core/hardware_properties.h"
#include "core/hle/kernel/init/init_slab_setup.h"
#include "core/hle/kernel/k_code_memory.h"
+#include "core/hle/kernel/k_debug.h"
#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_event_info.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_page_buffer.h"
@@ -22,6 +24,7 @@
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_shared_memory_info.h"
#include "core/hle/kernel/k_system_control.h"
+#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_thread_local_page.h"
#include "core/hle/kernel/k_transfer_memory.h"
@@ -44,7 +47,10 @@ namespace Kernel::Init {
HANDLER(KThreadLocalPage, \
(SLAB_COUNT(KProcess) + (SLAB_COUNT(KProcess) + SLAB_COUNT(KThread)) / 8), \
##__VA_ARGS__) \
- HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ##__VA_ARGS__)
+ HANDLER(KResourceLimit, (SLAB_COUNT(KResourceLimit)), ##__VA_ARGS__) \
+ HANDLER(KEventInfo, (SLAB_COUNT(KThread) + SLAB_COUNT(KDebug)), ##__VA_ARGS__) \
+ HANDLER(KDebug, (SLAB_COUNT(KDebug)), ##__VA_ARGS__) \
+ HANDLER(KSecureSystemResource, (SLAB_COUNT(KProcess)), ##__VA_ARGS__)
namespace {
@@ -73,8 +79,20 @@ constexpr size_t SlabCountKResourceLimit = 5;
constexpr size_t SlabCountKDebug = Core::Hardware::NUM_CPU_CORES;
constexpr size_t SlabCountKIoPool = 1;
constexpr size_t SlabCountKIoRegion = 6;
+constexpr size_t SlabcountKSessionRequestMappings = 40;
-constexpr size_t SlabCountExtraKThread = 160;
+constexpr size_t SlabCountExtraKThread = (1024 + 256 + 256) - SlabCountKThread;
+
+namespace test {
+
+static_assert(KernelPageBufferHeapSize ==
+ 2 * PageSize + (SlabCountKProcess + SlabCountKThread +
+ (SlabCountKProcess + SlabCountKThread) / 8) *
+ PageSize);
+static_assert(KernelPageBufferAdditionalSize ==
+ (SlabCountExtraKThread + (SlabCountExtraKThread / 8)) * PageSize);
+
+} // namespace test
/// Helper function to translate from the slab virtual address to the reserved location in physical
/// memory.
@@ -109,7 +127,7 @@ VAddr InitializeSlabHeap(Core::System& system, KMemoryLayout& memory_layout, VAd
}
size_t CalculateSlabHeapGapSize() {
- constexpr size_t KernelSlabHeapGapSize = 2_MiB - 296_KiB;
+ constexpr size_t KernelSlabHeapGapSize = 2_MiB - 320_KiB;
static_assert(KernelSlabHeapGapSize <= KernelSlabHeapGapsSizeMax);
return KernelSlabHeapGapSize;
}
@@ -134,6 +152,7 @@ KSlabResourceCounts KSlabResourceCounts::CreateDefault() {
.num_KDebug = SlabCountKDebug,
.num_KIoPool = SlabCountKIoPool,
.num_KIoRegion = SlabCountKIoRegion,
+ .num_KSessionRequestMappings = SlabcountKSessionRequestMappings,
};
}
@@ -164,29 +183,6 @@ size_t CalculateTotalSlabHeapSize(const KernelCore& kernel) {
return size;
}
-void InitializeKPageBufferSlabHeap(Core::System& system) {
- auto& kernel = system.Kernel();
-
- const auto& counts = kernel.SlabResourceCounts();
- const size_t num_pages =
- counts.num_KProcess + counts.num_KThread + (counts.num_KProcess + counts.num_KThread) / 8;
- const size_t slab_size = num_pages * PageSize;
-
- // Reserve memory from the system resource limit.
- ASSERT(kernel.GetSystemResourceLimit()->Reserve(LimitableResource::PhysicalMemory, slab_size));
-
- // Allocate memory for the slab.
- constexpr auto AllocateOption = KMemoryManager::EncodeOption(
- KMemoryManager::Pool::System, KMemoryManager::Direction::FromFront);
- const PAddr slab_address =
- kernel.MemoryManager().AllocateAndOpenContinuous(num_pages, 1, AllocateOption);
- ASSERT(slab_address != 0);
-
- // Initialize the slabheap.
- KPageBuffer::InitializeSlabHeap(kernel, system.DeviceMemory().GetPointer<void>(slab_address),
- slab_size);
-}
-
void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) {
auto& kernel = system.Kernel();
@@ -258,3 +254,30 @@ void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) {
}
} // namespace Kernel::Init
+
+namespace Kernel {
+
+void KPageBufferSlabHeap::Initialize(Core::System& system) {
+ auto& kernel = system.Kernel();
+ const auto& counts = kernel.SlabResourceCounts();
+ const size_t num_pages =
+ counts.num_KProcess + counts.num_KThread + (counts.num_KProcess + counts.num_KThread) / 8;
+ const size_t slab_size = num_pages * PageSize;
+
+ // Reserve memory from the system resource limit.
+ ASSERT(
+ kernel.GetSystemResourceLimit()->Reserve(LimitableResource::PhysicalMemoryMax, slab_size));
+
+ // Allocate memory for the slab.
+ constexpr auto AllocateOption = KMemoryManager::EncodeOption(
+ KMemoryManager::Pool::System, KMemoryManager::Direction::FromFront);
+ const PAddr slab_address =
+ kernel.MemoryManager().AllocateAndOpenContinuous(num_pages, 1, AllocateOption);
+ ASSERT(slab_address != 0);
+
+ // Initialize the slabheap.
+ KPageBuffer::InitializeSlabHeap(kernel, system.DeviceMemory().GetPointer<void>(slab_address),
+ slab_size);
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/init/init_slab_setup.h b/src/core/hle/kernel/init/init_slab_setup.h
index 13be63c87..5e22821bc 100644
--- a/src/core/hle/kernel/init/init_slab_setup.h
+++ b/src/core/hle/kernel/init/init_slab_setup.h
@@ -33,11 +33,11 @@ struct KSlabResourceCounts {
size_t num_KDebug;
size_t num_KIoPool;
size_t num_KIoRegion;
+ size_t num_KSessionRequestMappings;
};
void InitializeSlabResourceCounts(KernelCore& kernel);
size_t CalculateTotalSlabHeapSize(const KernelCore& kernel);
-void InitializeKPageBufferSlabHeap(Core::System& system);
void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout);
} // namespace Kernel::Init
diff --git a/src/core/hle/kernel/k_class_token.cpp b/src/core/hle/kernel/k_class_token.cpp
index 10265c23c..a850db3c4 100644
--- a/src/core/hle/kernel/k_class_token.cpp
+++ b/src/core/hle/kernel/k_class_token.cpp
@@ -16,6 +16,7 @@
#include "core/hle/kernel/k_session.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_transfer_memory.h"
@@ -119,4 +120,6 @@ static_assert(std::is_final_v<KTransferMemory> && std::is_base_of_v<KAutoObject,
// static_assert(std::is_final_v<KCodeMemory> &&
// std::is_base_of_v<KAutoObject, KCodeMemory>);
+static_assert(std::is_base_of_v<KAutoObject, KSystemResource>);
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_class_token.h b/src/core/hle/kernel/k_class_token.h
index ab20e00ff..e75b1c035 100644
--- a/src/core/hle/kernel/k_class_token.h
+++ b/src/core/hle/kernel/k_class_token.h
@@ -10,6 +10,8 @@ namespace Kernel {
class KAutoObject;
+class KSystemResource;
+
class KClassTokenGenerator {
public:
using TokenBaseType = u16;
@@ -58,7 +60,7 @@ private:
if constexpr (std::is_same<T, KAutoObject>::value) {
static_assert(T::ObjectType == ObjectType::KAutoObject);
return 0;
- } else if constexpr (!std::is_final<T>::value) {
+ } else if constexpr (!std::is_final<T>::value && !std::same_as<T, KSystemResource>) {
static_assert(ObjectType::BaseClassesStart <= T::ObjectType &&
T::ObjectType < ObjectType::BaseClassesEnd);
constexpr auto ClassIndex = static_cast<TokenBaseType>(T::ObjectType) -
@@ -108,6 +110,8 @@ public:
KSessionRequest,
KCodeMemory,
+ KSystemResource,
+
// NOTE: True order for these has not been determined yet.
KAlpha,
KBeta,
diff --git a/src/core/hle/kernel/k_client_port.cpp b/src/core/hle/kernel/k_client_port.cpp
index 3cb22ff4d..2ec623a58 100644
--- a/src/core/hle/kernel/k_client_port.cpp
+++ b/src/core/hle/kernel/k_client_port.cpp
@@ -58,11 +58,10 @@ bool KClientPort::IsSignaled() const {
return num_sessions < max_sessions;
}
-Result KClientPort::CreateSession(KClientSession** out,
- std::shared_ptr<SessionRequestManager> session_manager) {
+Result KClientPort::CreateSession(KClientSession** out) {
// Reserve a new session from the resource limit.
KScopedResourceReservation session_reservation(kernel.CurrentProcess()->GetResourceLimit(),
- LimitableResource::Sessions);
+ LimitableResource::SessionCountMax);
R_UNLESS(session_reservation.Succeeded(), ResultLimitReached);
// Update the session counts.
@@ -104,7 +103,7 @@ Result KClientPort::CreateSession(KClientSession** out,
}
// Initialize the session.
- session->Initialize(this, parent->GetName(), session_manager);
+ session->Initialize(this, parent->GetName());
// Commit the session reservation.
session_reservation.Commit();
diff --git a/src/core/hle/kernel/k_client_port.h b/src/core/hle/kernel/k_client_port.h
index e17eff28f..81046fb86 100644
--- a/src/core/hle/kernel/k_client_port.h
+++ b/src/core/hle/kernel/k_client_port.h
@@ -52,8 +52,7 @@ public:
void Destroy() override;
bool IsSignaled() const override;
- Result CreateSession(KClientSession** out,
- std::shared_ptr<SessionRequestManager> session_manager = nullptr);
+ Result CreateSession(KClientSession** out);
private:
std::atomic<s32> num_sessions{};
diff --git a/src/core/hle/kernel/k_debug.h b/src/core/hle/kernel/k_debug.h
new file mode 100644
index 000000000..e3a0689c8
--- /dev/null
+++ b/src/core/hle/kernel/k_debug.h
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/kernel/k_auto_object.h"
+#include "core/hle/kernel/slab_helpers.h"
+
+namespace Kernel {
+
+class KDebug final : public KAutoObjectWithSlabHeapAndContainer<KDebug, KAutoObjectWithList> {
+ KERNEL_AUTOOBJECT_TRAITS(KDebug, KAutoObject);
+
+public:
+ explicit KDebug(KernelCore& kernel_) : KAutoObjectWithSlabHeapAndContainer{kernel_} {}
+
+ static void PostDestroy([[maybe_unused]] uintptr_t arg) {}
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_dynamic_page_manager.h b/src/core/hle/kernel/k_dynamic_page_manager.h
index 9076c8fa3..ac80d60a1 100644
--- a/src/core/hle/kernel/k_dynamic_page_manager.h
+++ b/src/core/hle/kernel/k_dynamic_page_manager.h
@@ -3,6 +3,8 @@
#pragma once
+#include <vector>
+
#include "common/alignment.h"
#include "common/common_types.h"
#include "core/hle/kernel/k_page_bitmap.h"
@@ -33,28 +35,36 @@ public:
return reinterpret_cast<T*>(m_backing_memory.data() + (addr - m_address));
}
- Result Initialize(VAddr addr, size_t sz) {
+ Result Initialize(VAddr memory, size_t size, size_t align) {
// We need to have positive size.
- R_UNLESS(sz > 0, ResultOutOfMemory);
- m_backing_memory.resize(sz);
+ R_UNLESS(size > 0, ResultOutOfMemory);
+ m_backing_memory.resize(size);
+
+ // Set addresses.
+ m_address = memory;
+ m_aligned_address = Common::AlignDown(memory, align);
- // Calculate management overhead.
- const size_t management_size =
- KPageBitmap::CalculateManagementOverheadSize(sz / sizeof(PageBuffer));
- const size_t allocatable_size = sz - management_size;
+ // Calculate extents.
+ const size_t managed_size = m_address + size - m_aligned_address;
+ const size_t overhead_size = Common::AlignUp(
+ KPageBitmap::CalculateManagementOverheadSize(managed_size / sizeof(PageBuffer)),
+ sizeof(PageBuffer));
+ R_UNLESS(overhead_size < size, ResultOutOfMemory);
// Set tracking fields.
- m_address = addr;
- m_size = Common::AlignDown(allocatable_size, sizeof(PageBuffer));
- m_count = allocatable_size / sizeof(PageBuffer);
- R_UNLESS(m_count > 0, ResultOutOfMemory);
+ m_size = Common::AlignDown(size - overhead_size, sizeof(PageBuffer));
+ m_count = m_size / sizeof(PageBuffer);
// Clear the management region.
- u64* management_ptr = GetPointer<u64>(m_address + allocatable_size);
- std::memset(management_ptr, 0, management_size);
+ u64* management_ptr = GetPointer<u64>(m_address + size - overhead_size);
+ std::memset(management_ptr, 0, overhead_size);
// Initialize the bitmap.
- m_page_bitmap.Initialize(management_ptr, m_count);
+ const size_t allocatable_region_size =
+ (m_address + size - overhead_size) - m_aligned_address;
+ ASSERT(allocatable_region_size >= sizeof(PageBuffer));
+
+ m_page_bitmap.Initialize(management_ptr, allocatable_region_size / sizeof(PageBuffer));
// Free the pages to the bitmap.
for (size_t i = 0; i < m_count; i++) {
@@ -62,7 +72,8 @@ public:
std::memset(GetPointer<PageBuffer>(m_address) + i, 0, PageSize);
// Set the bit for the free page.
- m_page_bitmap.SetBit(i);
+ m_page_bitmap.SetBit((m_address + (i * sizeof(PageBuffer)) - m_aligned_address) /
+ sizeof(PageBuffer));
}
R_SUCCEED();
@@ -101,7 +112,28 @@ public:
m_page_bitmap.ClearBit(offset);
m_peak = std::max(m_peak, (++m_used));
- return GetPointer<PageBuffer>(m_address) + offset;
+ return GetPointer<PageBuffer>(m_aligned_address) + offset;
+ }
+
+ PageBuffer* Allocate(size_t count) {
+ // Take the lock.
+ // TODO(bunnei): We should disable interrupts here via KScopedInterruptDisable.
+ KScopedSpinLock lk(m_lock);
+
+ // Find a random free block.
+ s64 soffset = m_page_bitmap.FindFreeRange(count);
+ if (soffset < 0) [[likely]] {
+ return nullptr;
+ }
+
+ const size_t offset = static_cast<size_t>(soffset);
+
+ // Update our tracking.
+ m_page_bitmap.ClearRange(offset, count);
+ m_used += count;
+ m_peak = std::max(m_peak, m_used);
+
+ return GetPointer<PageBuffer>(m_aligned_address) + offset;
}
void Free(PageBuffer* pb) {
@@ -113,7 +145,7 @@ public:
KScopedSpinLock lk(m_lock);
// Set the bit for the free page.
- size_t offset = (reinterpret_cast<uintptr_t>(pb) - m_address) / sizeof(PageBuffer);
+ size_t offset = (reinterpret_cast<uintptr_t>(pb) - m_aligned_address) / sizeof(PageBuffer);
m_page_bitmap.SetBit(offset);
// Decrement our used count.
@@ -127,6 +159,7 @@ private:
size_t m_peak{};
size_t m_count{};
VAddr m_address{};
+ VAddr m_aligned_address{};
size_t m_size{};
// TODO(bunnei): Back by host memory until we emulate kernel virtual address space.
diff --git a/src/core/hle/kernel/k_dynamic_resource_manager.h b/src/core/hle/kernel/k_dynamic_resource_manager.h
index 1ce517e8e..b6a27d648 100644
--- a/src/core/hle/kernel/k_dynamic_resource_manager.h
+++ b/src/core/hle/kernel/k_dynamic_resource_manager.h
@@ -6,6 +6,7 @@
#include "common/common_funcs.h"
#include "core/hle/kernel/k_dynamic_slab_heap.h"
#include "core/hle/kernel/k_memory_block.h"
+#include "core/hle/kernel/k_page_group.h"
namespace Kernel {
@@ -51,8 +52,10 @@ private:
DynamicSlabType* m_slab_heap{};
};
+class KBlockInfoManager : public KDynamicResourceManager<KBlockInfo> {};
class KMemoryBlockSlabManager : public KDynamicResourceManager<KMemoryBlock> {};
+using KBlockInfoSlabHeap = typename KBlockInfoManager::DynamicSlabType;
using KMemoryBlockSlabHeap = typename KMemoryBlockSlabManager::DynamicSlabType;
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_event.cpp b/src/core/hle/kernel/k_event.cpp
index 78ca59463..27f70e5c5 100644
--- a/src/core/hle/kernel/k_event.cpp
+++ b/src/core/hle/kernel/k_event.cpp
@@ -50,7 +50,7 @@ Result KEvent::Clear() {
void KEvent::PostDestroy(uintptr_t arg) {
// Release the event count resource the owner process holds.
KProcess* owner = reinterpret_cast<KProcess*>(arg);
- owner->GetResourceLimit()->Release(LimitableResource::Events, 1);
+ owner->GetResourceLimit()->Release(LimitableResource::EventCountMax, 1);
owner->Close();
}
diff --git a/src/core/hle/kernel/k_event_info.h b/src/core/hle/kernel/k_event_info.h
new file mode 100644
index 000000000..25b3ff594
--- /dev/null
+++ b/src/core/hle/kernel/k_event_info.h
@@ -0,0 +1,64 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include <boost/intrusive/list.hpp>
+
+#include "core/hle/kernel/slab_helpers.h"
+#include "core/hle/kernel/svc_types.h"
+
+namespace Kernel {
+
+class KEventInfo : public KSlabAllocated<KEventInfo>, public boost::intrusive::list_base_hook<> {
+public:
+ struct InfoCreateThread {
+ u32 thread_id{};
+ uintptr_t tls_address{};
+ };
+
+ struct InfoExitProcess {
+ Svc::ProcessExitReason reason{};
+ };
+
+ struct InfoExitThread {
+ Svc::ThreadExitReason reason{};
+ };
+
+ struct InfoException {
+ Svc::DebugException exception_type{};
+ s32 exception_data_count{};
+ uintptr_t exception_address{};
+ std::array<uintptr_t, 4> exception_data{};
+ };
+
+ struct InfoSystemCall {
+ s64 tick{};
+ s32 id{};
+ };
+
+public:
+ KEventInfo() = default;
+ ~KEventInfo() = default;
+
+public:
+ Svc::DebugEvent event{};
+ u32 thread_id{};
+ u32 flags{};
+ bool is_attached{};
+ bool continue_flag{};
+ bool ignore_continue{};
+ bool close_once{};
+ union {
+ InfoCreateThread create_thread;
+ InfoExitProcess exit_process;
+ InfoExitThread exit_thread;
+ InfoException exception;
+ InfoSystemCall system_call;
+ } info{};
+ KThread* debug_thread{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_handle_table.cpp b/src/core/hle/kernel/k_handle_table.cpp
index e830ca46e..1c7a766c8 100644
--- a/src/core/hle/kernel/k_handle_table.cpp
+++ b/src/core/hle/kernel/k_handle_table.cpp
@@ -5,14 +5,11 @@
namespace Kernel {
-KHandleTable::KHandleTable(KernelCore& kernel_) : kernel{kernel_} {}
-KHandleTable::~KHandleTable() = default;
-
Result KHandleTable::Finalize() {
// Get the table and clear our record of it.
u16 saved_table_size = 0;
{
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
std::swap(m_table_size, saved_table_size);
@@ -25,28 +22,28 @@ Result KHandleTable::Finalize() {
}
}
- return ResultSuccess;
+ R_SUCCEED();
}
bool KHandleTable::Remove(Handle handle) {
// Don't allow removal of a pseudo-handle.
- if (Svc::IsPseudoHandle(handle)) {
+ if (Svc::IsPseudoHandle(handle)) [[unlikely]] {
return false;
}
// Handles must not have reserved bits set.
const auto handle_pack = HandlePack(handle);
- if (handle_pack.reserved != 0) {
+ if (handle_pack.reserved != 0) [[unlikely]] {
return false;
}
// Find the object and free the entry.
KAutoObject* obj = nullptr;
{
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
- if (this->IsValidHandle(handle)) {
+ if (this->IsValidHandle(handle)) [[likely]] {
const auto index = handle_pack.index;
obj = m_objects[index];
@@ -57,13 +54,13 @@ bool KHandleTable::Remove(Handle handle) {
}
// Close the object.
- kernel.UnregisterInUseObject(obj);
+ m_kernel.UnregisterInUseObject(obj);
obj->Close();
return true;
}
Result KHandleTable::Add(Handle* out_handle, KAutoObject* obj) {
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
// Never exceed our capacity.
@@ -82,22 +79,22 @@ Result KHandleTable::Add(Handle* out_handle, KAutoObject* obj) {
*out_handle = EncodeHandle(static_cast<u16>(index), linear_id);
}
- return ResultSuccess;
+ R_SUCCEED();
}
Result KHandleTable::Reserve(Handle* out_handle) {
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
// Never exceed our capacity.
R_UNLESS(m_count < m_table_size, ResultOutOfHandles);
*out_handle = EncodeHandle(static_cast<u16>(this->AllocateEntry()), this->AllocateLinearId());
- return ResultSuccess;
+ R_SUCCEED();
}
void KHandleTable::Unreserve(Handle handle) {
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
// Unpack the handle.
@@ -108,7 +105,7 @@ void KHandleTable::Unreserve(Handle handle) {
ASSERT(reserved == 0);
ASSERT(linear_id != 0);
- if (index < m_table_size) {
+ if (index < m_table_size) [[likely]] {
// NOTE: This code does not check the linear id.
ASSERT(m_objects[index] == nullptr);
this->FreeEntry(index);
@@ -116,7 +113,7 @@ void KHandleTable::Unreserve(Handle handle) {
}
void KHandleTable::Register(Handle handle, KAutoObject* obj) {
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
// Unpack the handle.
@@ -127,7 +124,7 @@ void KHandleTable::Register(Handle handle, KAutoObject* obj) {
ASSERT(reserved == 0);
ASSERT(linear_id != 0);
- if (index < m_table_size) {
+ if (index < m_table_size) [[likely]] {
// Set the entry.
ASSERT(m_objects[index] == nullptr);
diff --git a/src/core/hle/kernel/k_handle_table.h b/src/core/hle/kernel/k_handle_table.h
index 0864a737c..65cae3b27 100644
--- a/src/core/hle/kernel/k_handle_table.h
+++ b/src/core/hle/kernel/k_handle_table.h
@@ -21,33 +21,38 @@ namespace Kernel {
class KernelCore;
class KHandleTable {
-public:
YUZU_NON_COPYABLE(KHandleTable);
YUZU_NON_MOVEABLE(KHandleTable);
+public:
static constexpr size_t MaxTableSize = 1024;
- explicit KHandleTable(KernelCore& kernel_);
- ~KHandleTable();
+public:
+ explicit KHandleTable(KernelCore& kernel) : m_kernel(kernel) {}
Result Initialize(s32 size) {
+ // Check that the table size is valid.
R_UNLESS(size <= static_cast<s32>(MaxTableSize), ResultOutOfMemory);
+ // Lock.
+ KScopedDisableDispatch dd{m_kernel};
+ KScopedSpinLock lk(m_lock);
+
// Initialize all fields.
m_max_count = 0;
- m_table_size = static_cast<u16>((size <= 0) ? MaxTableSize : size);
+ m_table_size = static_cast<s16>((size <= 0) ? MaxTableSize : size);
m_next_linear_id = MinLinearId;
m_count = 0;
m_free_head_index = -1;
// Free all entries.
- for (s16 i = 0; i < static_cast<s16>(m_table_size); ++i) {
+ for (s32 i = 0; i < static_cast<s32>(m_table_size); ++i) {
m_objects[i] = nullptr;
- m_entry_infos[i].next_free_index = i - 1;
+ m_entry_infos[i].next_free_index = static_cast<s16>(i - 1);
m_free_head_index = i;
}
- return ResultSuccess;
+ R_SUCCEED();
}
size_t GetTableSize() const {
@@ -66,13 +71,13 @@ public:
template <typename T = KAutoObject>
KScopedAutoObject<T> GetObjectWithoutPseudoHandle(Handle handle) const {
// Lock and look up in table.
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
if constexpr (std::is_same_v<T, KAutoObject>) {
return this->GetObjectImpl(handle);
} else {
- if (auto* obj = this->GetObjectImpl(handle); obj != nullptr) {
+ if (auto* obj = this->GetObjectImpl(handle); obj != nullptr) [[likely]] {
return obj->DynamicCast<T*>();
} else {
return nullptr;
@@ -85,13 +90,13 @@ public:
// Handle pseudo-handles.
if constexpr (std::derived_from<KProcess, T>) {
if (handle == Svc::PseudoHandle::CurrentProcess) {
- auto* const cur_process = kernel.CurrentProcess();
+ auto* const cur_process = m_kernel.CurrentProcess();
ASSERT(cur_process != nullptr);
return cur_process;
}
} else if constexpr (std::derived_from<KThread, T>) {
if (handle == Svc::PseudoHandle::CurrentThread) {
- auto* const cur_thread = GetCurrentThreadPointer(kernel);
+ auto* const cur_thread = GetCurrentThreadPointer(m_kernel);
ASSERT(cur_thread != nullptr);
return cur_thread;
}
@@ -100,6 +105,37 @@ public:
return this->template GetObjectWithoutPseudoHandle<T>(handle);
}
+ KScopedAutoObject<KAutoObject> GetObjectForIpcWithoutPseudoHandle(Handle handle) const {
+ // Lock and look up in table.
+ KScopedDisableDispatch dd{m_kernel};
+ KScopedSpinLock lk(m_lock);
+
+ return this->GetObjectImpl(handle);
+ }
+
+ KScopedAutoObject<KAutoObject> GetObjectForIpc(Handle handle, KThread* cur_thread) const {
+ // Handle pseudo-handles.
+ ASSERT(cur_thread != nullptr);
+ if (handle == Svc::PseudoHandle::CurrentProcess) {
+ auto* const cur_process =
+ static_cast<KAutoObject*>(static_cast<void*>(cur_thread->GetOwnerProcess()));
+ ASSERT(cur_process != nullptr);
+ return cur_process;
+ }
+ if (handle == Svc::PseudoHandle::CurrentThread) {
+ return static_cast<KAutoObject*>(cur_thread);
+ }
+
+ return GetObjectForIpcWithoutPseudoHandle(handle);
+ }
+
+ KScopedAutoObject<KAutoObject> GetObjectByIndex(Handle* out_handle, size_t index) const {
+ KScopedDisableDispatch dd{m_kernel};
+ KScopedSpinLock lk(m_lock);
+
+ return this->GetObjectByIndexImpl(out_handle, index);
+ }
+
Result Reserve(Handle* out_handle);
void Unreserve(Handle handle);
@@ -112,7 +148,7 @@ public:
size_t num_opened;
{
// Lock the table.
- KScopedDisableDispatch dd(kernel);
+ KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk(m_lock);
for (num_opened = 0; num_opened < num_handles; num_opened++) {
// Get the current handle.
@@ -120,13 +156,13 @@ public:
// Get the object for the current handle.
KAutoObject* cur_object = this->GetObjectImpl(cur_handle);
- if (cur_object == nullptr) {
+ if (cur_object == nullptr) [[unlikely]] {
break;
}
// Cast the current object to the desired type.
T* cur_t = cur_object->DynamicCast<T*>();
- if (cur_t == nullptr) {
+ if (cur_t == nullptr) [[unlikely]] {
break;
}
@@ -137,7 +173,7 @@ public:
}
// If we converted every object, succeed.
- if (num_opened == num_handles) {
+ if (num_opened == num_handles) [[likely]] {
return true;
}
@@ -191,21 +227,21 @@ private:
ASSERT(reserved == 0);
// Validate our indexing information.
- if (raw_value == 0) {
+ if (raw_value == 0) [[unlikely]] {
return false;
}
- if (linear_id == 0) {
+ if (linear_id == 0) [[unlikely]] {
return false;
}
- if (index >= m_table_size) {
+ if (index >= m_table_size) [[unlikely]] {
return false;
}
// Check that there's an object, and our serial id is correct.
- if (m_objects[index] == nullptr) {
+ if (m_objects[index] == nullptr) [[unlikely]] {
return false;
}
- if (m_entry_infos[index].GetLinearId() != linear_id) {
+ if (m_entry_infos[index].GetLinearId() != linear_id) [[unlikely]] {
return false;
}
@@ -215,11 +251,11 @@ private:
KAutoObject* GetObjectImpl(Handle handle) const {
// Handles must not have reserved bits set.
const auto handle_pack = HandlePack(handle);
- if (handle_pack.reserved != 0) {
+ if (handle_pack.reserved != 0) [[unlikely]] {
return nullptr;
}
- if (this->IsValidHandle(handle)) {
+ if (this->IsValidHandle(handle)) [[likely]] {
return m_objects[handle_pack.index];
} else {
return nullptr;
@@ -227,9 +263,8 @@ private:
}
KAutoObject* GetObjectByIndexImpl(Handle* out_handle, size_t index) const {
-
// Index must be in bounds.
- if (index >= m_table_size) {
+ if (index >= m_table_size) [[unlikely]] {
return nullptr;
}
@@ -244,18 +279,15 @@ private:
private:
union HandlePack {
- HandlePack() = default;
- HandlePack(Handle handle) : raw{static_cast<u32>(handle)} {}
+ constexpr HandlePack() = default;
+ constexpr HandlePack(Handle handle) : raw{static_cast<u32>(handle)} {}
- u32 raw;
+ u32 raw{};
BitField<0, 15, u32> index;
BitField<15, 15, u32> linear_id;
BitField<30, 2, u32> reserved;
};
- static constexpr u16 MinLinearId = 1;
- static constexpr u16 MaxLinearId = 0x7FFF;
-
static constexpr Handle EncodeHandle(u16 index, u16 linear_id) {
HandlePack handle{};
handle.index.Assign(index);
@@ -264,6 +296,10 @@ private:
return handle.raw;
}
+private:
+ static constexpr u16 MinLinearId = 1;
+ static constexpr u16 MaxLinearId = 0x7FFF;
+
union EntryInfo {
u16 linear_id;
s16 next_free_index;
@@ -271,21 +307,21 @@ private:
constexpr u16 GetLinearId() const {
return linear_id;
}
- constexpr s16 GetNextFreeIndex() const {
+ constexpr s32 GetNextFreeIndex() const {
return next_free_index;
}
};
private:
+ KernelCore& m_kernel;
std::array<EntryInfo, MaxTableSize> m_entry_infos{};
std::array<KAutoObject*, MaxTableSize> m_objects{};
- s32 m_free_head_index{-1};
+ mutable KSpinLock m_lock;
+ s32 m_free_head_index{};
u16 m_table_size{};
u16 m_max_count{};
- u16 m_next_linear_id{MinLinearId};
+ u16 m_next_linear_id{};
u16 m_count{};
- mutable KSpinLock m_lock;
- KernelCore& kernel;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_memory_block.h b/src/core/hle/kernel/k_memory_block.h
index 9444f6bd2..3b6e7baff 100644
--- a/src/core/hle/kernel/k_memory_block.h
+++ b/src/core/hle/kernel/k_memory_block.h
@@ -35,26 +35,32 @@ enum class KMemoryState : u32 {
FlagCanMapProcess = (1 << 23),
FlagCanChangeAttribute = (1 << 24),
FlagCanCodeMemory = (1 << 25),
+ FlagLinearMapped = (1 << 26),
FlagsData = FlagCanReprotect | FlagCanUseIpc | FlagCanUseNonDeviceIpc | FlagCanUseNonSecureIpc |
FlagMapped | FlagCanAlias | FlagCanTransfer | FlagCanQueryPhysical |
FlagCanDeviceMap | FlagCanAlignedDeviceMap | FlagCanIpcUserBuffer |
- FlagReferenceCounted | FlagCanChangeAttribute,
+ FlagReferenceCounted | FlagCanChangeAttribute | FlagLinearMapped,
FlagsCode = FlagCanDebug | FlagCanUseIpc | FlagCanUseNonDeviceIpc | FlagCanUseNonSecureIpc |
FlagMapped | FlagCode | FlagCanQueryPhysical | FlagCanDeviceMap |
- FlagCanAlignedDeviceMap | FlagReferenceCounted,
+ FlagCanAlignedDeviceMap | FlagReferenceCounted | FlagLinearMapped,
- FlagsMisc = FlagMapped | FlagReferenceCounted | FlagCanQueryPhysical | FlagCanDeviceMap,
+ FlagsMisc = FlagMapped | FlagReferenceCounted | FlagCanQueryPhysical | FlagCanDeviceMap |
+ FlagLinearMapped,
Free = static_cast<u32>(Svc::MemoryState::Free),
- Io = static_cast<u32>(Svc::MemoryState::Io) | FlagMapped,
+ Io = static_cast<u32>(Svc::MemoryState::Io) | FlagMapped | FlagCanDeviceMap |
+ FlagCanAlignedDeviceMap,
Static = static_cast<u32>(Svc::MemoryState::Static) | FlagMapped | FlagCanQueryPhysical,
Code = static_cast<u32>(Svc::MemoryState::Code) | FlagsCode | FlagCanMapProcess,
CodeData = static_cast<u32>(Svc::MemoryState::CodeData) | FlagsData | FlagCanMapProcess |
FlagCanCodeMemory,
- Shared = static_cast<u32>(Svc::MemoryState::Shared) | FlagMapped | FlagReferenceCounted,
Normal = static_cast<u32>(Svc::MemoryState::Normal) | FlagsData | FlagCanCodeMemory,
+ Shared = static_cast<u32>(Svc::MemoryState::Shared) | FlagMapped | FlagReferenceCounted |
+ FlagLinearMapped,
+
+ // Alias was removed after 1.0.0.
AliasCode = static_cast<u32>(Svc::MemoryState::AliasCode) | FlagsCode | FlagCanMapProcess |
FlagCanCodeAlias,
@@ -67,18 +73,18 @@ enum class KMemoryState : u32 {
Stack = static_cast<u32>(Svc::MemoryState::Stack) | FlagsMisc | FlagCanAlignedDeviceMap |
FlagCanUseIpc | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
- ThreadLocal =
- static_cast<u32>(Svc::MemoryState::ThreadLocal) | FlagMapped | FlagReferenceCounted,
+ ThreadLocal = static_cast<u32>(Svc::MemoryState::ThreadLocal) | FlagMapped | FlagLinearMapped,
- Transfered = static_cast<u32>(Svc::MemoryState::Transferred) | FlagsMisc |
+ Transfered = static_cast<u32>(Svc::MemoryState::Transfered) | FlagsMisc |
FlagCanAlignedDeviceMap | FlagCanChangeAttribute | FlagCanUseIpc |
FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
- SharedTransfered = static_cast<u32>(Svc::MemoryState::SharedTransferred) | FlagsMisc |
+ SharedTransfered = static_cast<u32>(Svc::MemoryState::SharedTransfered) | FlagsMisc |
FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
SharedCode = static_cast<u32>(Svc::MemoryState::SharedCode) | FlagMapped |
- FlagReferenceCounted | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
+ FlagReferenceCounted | FlagLinearMapped | FlagCanUseNonSecureIpc |
+ FlagCanUseNonDeviceIpc,
Inaccessible = static_cast<u32>(Svc::MemoryState::Inaccessible),
@@ -91,69 +97,69 @@ enum class KMemoryState : u32 {
Kernel = static_cast<u32>(Svc::MemoryState::Kernel) | FlagMapped,
GeneratedCode = static_cast<u32>(Svc::MemoryState::GeneratedCode) | FlagMapped |
- FlagReferenceCounted | FlagCanDebug,
- CodeOut = static_cast<u32>(Svc::MemoryState::CodeOut) | FlagMapped | FlagReferenceCounted,
+ FlagReferenceCounted | FlagCanDebug | FlagLinearMapped,
+ CodeOut = static_cast<u32>(Svc::MemoryState::CodeOut) | FlagMapped | FlagReferenceCounted |
+ FlagLinearMapped,
Coverage = static_cast<u32>(Svc::MemoryState::Coverage) | FlagMapped,
+
+ Insecure = static_cast<u32>(Svc::MemoryState::Insecure) | FlagMapped | FlagReferenceCounted |
+ FlagLinearMapped | FlagCanChangeAttribute | FlagCanDeviceMap |
+ FlagCanAlignedDeviceMap | FlagCanUseNonSecureIpc | FlagCanUseNonDeviceIpc,
};
DECLARE_ENUM_FLAG_OPERATORS(KMemoryState);
static_assert(static_cast<u32>(KMemoryState::Free) == 0x00000000);
-static_assert(static_cast<u32>(KMemoryState::Io) == 0x00002001);
+static_assert(static_cast<u32>(KMemoryState::Io) == 0x00182001);
static_assert(static_cast<u32>(KMemoryState::Static) == 0x00042002);
-static_assert(static_cast<u32>(KMemoryState::Code) == 0x00DC7E03);
-static_assert(static_cast<u32>(KMemoryState::CodeData) == 0x03FEBD04);
-static_assert(static_cast<u32>(KMemoryState::Normal) == 0x037EBD05);
-static_assert(static_cast<u32>(KMemoryState::Shared) == 0x00402006);
-static_assert(static_cast<u32>(KMemoryState::AliasCode) == 0x00DD7E08);
-static_assert(static_cast<u32>(KMemoryState::AliasCodeData) == 0x03FFBD09);
-static_assert(static_cast<u32>(KMemoryState::Ipc) == 0x005C3C0A);
-static_assert(static_cast<u32>(KMemoryState::Stack) == 0x005C3C0B);
-static_assert(static_cast<u32>(KMemoryState::ThreadLocal) == 0x0040200C);
-static_assert(static_cast<u32>(KMemoryState::Transfered) == 0x015C3C0D);
-static_assert(static_cast<u32>(KMemoryState::SharedTransfered) == 0x005C380E);
-static_assert(static_cast<u32>(KMemoryState::SharedCode) == 0x0040380F);
+static_assert(static_cast<u32>(KMemoryState::Code) == 0x04DC7E03);
+static_assert(static_cast<u32>(KMemoryState::CodeData) == 0x07FEBD04);
+static_assert(static_cast<u32>(KMemoryState::Normal) == 0x077EBD05);
+static_assert(static_cast<u32>(KMemoryState::Shared) == 0x04402006);
+
+static_assert(static_cast<u32>(KMemoryState::AliasCode) == 0x04DD7E08);
+static_assert(static_cast<u32>(KMemoryState::AliasCodeData) == 0x07FFBD09);
+static_assert(static_cast<u32>(KMemoryState::Ipc) == 0x045C3C0A);
+static_assert(static_cast<u32>(KMemoryState::Stack) == 0x045C3C0B);
+static_assert(static_cast<u32>(KMemoryState::ThreadLocal) == 0x0400200C);
+static_assert(static_cast<u32>(KMemoryState::Transfered) == 0x055C3C0D);
+static_assert(static_cast<u32>(KMemoryState::SharedTransfered) == 0x045C380E);
+static_assert(static_cast<u32>(KMemoryState::SharedCode) == 0x0440380F);
static_assert(static_cast<u32>(KMemoryState::Inaccessible) == 0x00000010);
-static_assert(static_cast<u32>(KMemoryState::NonSecureIpc) == 0x005C3811);
-static_assert(static_cast<u32>(KMemoryState::NonDeviceIpc) == 0x004C2812);
+static_assert(static_cast<u32>(KMemoryState::NonSecureIpc) == 0x045C3811);
+static_assert(static_cast<u32>(KMemoryState::NonDeviceIpc) == 0x044C2812);
static_assert(static_cast<u32>(KMemoryState::Kernel) == 0x00002013);
-static_assert(static_cast<u32>(KMemoryState::GeneratedCode) == 0x00402214);
-static_assert(static_cast<u32>(KMemoryState::CodeOut) == 0x00402015);
+static_assert(static_cast<u32>(KMemoryState::GeneratedCode) == 0x04402214);
+static_assert(static_cast<u32>(KMemoryState::CodeOut) == 0x04402015);
static_assert(static_cast<u32>(KMemoryState::Coverage) == 0x00002016);
+static_assert(static_cast<u32>(KMemoryState::Insecure) == 0x05583817);
enum class KMemoryPermission : u8 {
None = 0,
All = static_cast<u8>(~None),
- Read = 1 << 0,
- Write = 1 << 1,
- Execute = 1 << 2,
-
- ReadAndWrite = Read | Write,
- ReadAndExecute = Read | Execute,
-
- UserMask = static_cast<u8>(Svc::MemoryPermission::Read | Svc::MemoryPermission::Write |
- Svc::MemoryPermission::Execute),
-
KernelShift = 3,
- KernelRead = Read << KernelShift,
- KernelWrite = Write << KernelShift,
- KernelExecute = Execute << KernelShift,
+ KernelRead = static_cast<u8>(Svc::MemoryPermission::Read) << KernelShift,
+ KernelWrite = static_cast<u8>(Svc::MemoryPermission::Write) << KernelShift,
+ KernelExecute = static_cast<u8>(Svc::MemoryPermission::Execute) << KernelShift,
NotMapped = (1 << (2 * KernelShift)),
KernelReadWrite = KernelRead | KernelWrite,
KernelReadExecute = KernelRead | KernelExecute,
- UserRead = Read | KernelRead,
- UserWrite = Write | KernelWrite,
- UserExecute = Execute,
+ UserRead = static_cast<u8>(Svc::MemoryPermission::Read) | KernelRead,
+ UserWrite = static_cast<u8>(Svc::MemoryPermission::Write) | KernelWrite,
+ UserExecute = static_cast<u8>(Svc::MemoryPermission::Execute),
UserReadWrite = UserRead | UserWrite,
UserReadExecute = UserRead | UserExecute,
- IpcLockChangeMask = NotMapped | UserReadWrite
+ UserMask = static_cast<u8>(Svc::MemoryPermission::Read | Svc::MemoryPermission::Write |
+ Svc::MemoryPermission::Execute),
+
+ IpcLockChangeMask = NotMapped | UserReadWrite,
};
DECLARE_ENUM_FLAG_OPERATORS(KMemoryPermission);
@@ -210,13 +216,15 @@ struct KMemoryInfo {
constexpr Svc::MemoryInfo GetSvcMemoryInfo() const {
return {
- .addr = m_address,
+ .base_address = m_address,
.size = m_size,
.state = static_cast<Svc::MemoryState>(m_state & KMemoryState::Mask),
- .attr = static_cast<Svc::MemoryAttribute>(m_attribute & KMemoryAttribute::UserMask),
- .perm = static_cast<Svc::MemoryPermission>(m_permission & KMemoryPermission::UserMask),
- .ipc_refcount = m_ipc_lock_count,
- .device_refcount = m_device_use_count,
+ .attribute =
+ static_cast<Svc::MemoryAttribute>(m_attribute & KMemoryAttribute::UserMask),
+ .permission =
+ static_cast<Svc::MemoryPermission>(m_permission & KMemoryPermission::UserMask),
+ .ipc_count = m_ipc_lock_count,
+ .device_count = m_device_use_count,
.padding = {},
};
}
@@ -468,6 +476,7 @@ public:
constexpr void UpdateDeviceDisableMergeStateForShareLeft(
[[maybe_unused]] KMemoryPermission new_perm, bool left, [[maybe_unused]] bool right) {
+ // New permission/right aren't used.
if (left) {
m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(
m_disable_merge_attribute | KMemoryBlockDisableMergeAttribute::DeviceLeft);
@@ -478,6 +487,7 @@ public:
constexpr void UpdateDeviceDisableMergeStateForShareRight(
[[maybe_unused]] KMemoryPermission new_perm, [[maybe_unused]] bool left, bool right) {
+ // New permission/left aren't used.
if (right) {
m_disable_merge_attribute = static_cast<KMemoryBlockDisableMergeAttribute>(
m_disable_merge_attribute | KMemoryBlockDisableMergeAttribute::DeviceRight);
@@ -494,6 +504,8 @@ public:
constexpr void ShareToDevice([[maybe_unused]] KMemoryPermission new_perm, bool left,
bool right) {
+ // New permission isn't used.
+
// We must either be shared or have a zero lock count.
ASSERT((m_attribute & KMemoryAttribute::DeviceShared) == KMemoryAttribute::DeviceShared ||
m_device_use_count == 0);
@@ -509,6 +521,7 @@ public:
constexpr void UpdateDeviceDisableMergeStateForUnshareLeft(
[[maybe_unused]] KMemoryPermission new_perm, bool left, [[maybe_unused]] bool right) {
+ // New permission/right aren't used.
if (left) {
if (!m_device_disable_merge_left_count) {
@@ -528,6 +541,8 @@ public:
constexpr void UpdateDeviceDisableMergeStateForUnshareRight(
[[maybe_unused]] KMemoryPermission new_perm, [[maybe_unused]] bool left, bool right) {
+ // New permission/left aren't used.
+
if (right) {
const u16 old_device_disable_merge_right_count = m_device_disable_merge_right_count--;
ASSERT(old_device_disable_merge_right_count > 0);
@@ -546,6 +561,8 @@ public:
constexpr void UnshareToDevice([[maybe_unused]] KMemoryPermission new_perm, bool left,
bool right) {
+ // New permission isn't used.
+
// We must be shared.
ASSERT((m_attribute & KMemoryAttribute::DeviceShared) == KMemoryAttribute::DeviceShared);
@@ -563,6 +580,7 @@ public:
constexpr void UnshareToDeviceRight([[maybe_unused]] KMemoryPermission new_perm, bool left,
bool right) {
+ // New permission isn't used.
// We must be shared.
ASSERT((m_attribute & KMemoryAttribute::DeviceShared) == KMemoryAttribute::DeviceShared);
@@ -613,6 +631,8 @@ public:
constexpr void UnlockForIpc([[maybe_unused]] KMemoryPermission new_perm, bool left,
[[maybe_unused]] bool right) {
+ // New permission isn't used.
+
// We must be locked.
ASSERT((m_attribute & KMemoryAttribute::IpcLocked) == KMemoryAttribute::IpcLocked);
diff --git a/src/core/hle/kernel/k_memory_layout.cpp b/src/core/hle/kernel/k_memory_layout.cpp
index 55dc296d0..72c3ee4b7 100644
--- a/src/core/hle/kernel/k_memory_layout.cpp
+++ b/src/core/hle/kernel/k_memory_layout.cpp
@@ -153,13 +153,9 @@ void KMemoryLayout::InitializeLinearMemoryRegionTrees(PAddr aligned_linear_phys_
}
}
-size_t KMemoryLayout::GetResourceRegionSizeForInit() {
- // Calculate resource region size based on whether we allow extra threads.
- const bool use_extra_resources = KSystemControl::Init::ShouldIncreaseThreadResourceLimit();
- size_t resource_region_size =
- KernelResourceSize + (use_extra_resources ? KernelSlabHeapAdditionalSize : 0);
-
- return resource_region_size;
+size_t KMemoryLayout::GetResourceRegionSizeForInit(bool use_extra_resource) {
+ return KernelResourceSize + KSystemControl::SecureAppletMemorySize +
+ (use_extra_resource ? KernelSlabHeapAdditionalSize + KernelPageBufferAdditionalSize : 0);
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_memory_layout.h b/src/core/hle/kernel/k_memory_layout.h
index 884fc623a..fd6e1d3e6 100644
--- a/src/core/hle/kernel/k_memory_layout.h
+++ b/src/core/hle/kernel/k_memory_layout.h
@@ -60,10 +60,12 @@ constexpr std::size_t KernelSlabHeapGapsSizeMax = 2_MiB - 64_KiB;
constexpr std::size_t KernelSlabHeapSize = KernelSlabHeapDataSize + KernelSlabHeapGapsSizeMax;
// NOTE: This is calculated from KThread slab counts, assuming KThread size <= 0x860.
-constexpr std::size_t KernelSlabHeapAdditionalSize = 0x68000;
+constexpr size_t KernelPageBufferHeapSize = 0x3E0000;
+constexpr size_t KernelSlabHeapAdditionalSize = 0x148000;
+constexpr size_t KernelPageBufferAdditionalSize = 0x33C000;
-constexpr std::size_t KernelResourceSize =
- KernelPageTableHeapSize + KernelInitialPageHeapSize + KernelSlabHeapSize;
+constexpr std::size_t KernelResourceSize = KernelPageTableHeapSize + KernelInitialPageHeapSize +
+ KernelSlabHeapSize + KernelPageBufferHeapSize;
constexpr bool IsKernelAddressKey(VAddr key) {
return KernelVirtualAddressSpaceBase <= key && key <= KernelVirtualAddressSpaceLast;
@@ -168,6 +170,11 @@ public:
KMemoryRegionType_VirtualDramKernelTraceBuffer));
}
+ const KMemoryRegion& GetSecureAppletMemoryRegion() {
+ return Dereference(GetVirtualMemoryRegionTree().FindByType(
+ KMemoryRegionType_VirtualDramKernelSecureAppletMemory));
+ }
+
const KMemoryRegion& GetVirtualLinearRegion(VAddr address) const {
return Dereference(FindVirtualLinear(address));
}
@@ -229,7 +236,7 @@ public:
void InitializeLinearMemoryRegionTrees(PAddr aligned_linear_phys_start,
VAddr linear_virtual_start);
- static size_t GetResourceRegionSizeForInit();
+ static size_t GetResourceRegionSizeForInit(bool use_extra_resource);
auto GetKernelRegionExtents() const {
return GetVirtualMemoryRegionTree().GetDerivedRegionExtents(KMemoryRegionType_Kernel);
@@ -279,6 +286,10 @@ public:
return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(
KMemoryRegionType_DramKernelSlab);
}
+ auto GetKernelSecureAppletMemoryRegionPhysicalExtents() {
+ return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(
+ KMemoryRegionType_DramKernelSecureAppletMemory);
+ }
auto GetKernelPageTableHeapRegionPhysicalExtents() const {
return GetPhysicalMemoryRegionTree().GetDerivedRegionExtents(
KMemoryRegionType_DramKernelPtHeap);
diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp
index 646711505..c4bf306e8 100644
--- a/src/core/hle/kernel/k_memory_manager.cpp
+++ b/src/core/hle/kernel/k_memory_manager.cpp
@@ -29,43 +29,44 @@ constexpr KMemoryManager::Pool GetPoolFromMemoryRegionType(u32 type) {
} else if ((type | KMemoryRegionType_DramSystemNonSecurePool) == type) {
return KMemoryManager::Pool::SystemNonSecure;
} else {
- ASSERT_MSG(false, "InvalidMemoryRegionType for conversion to Pool");
- return {};
+ UNREACHABLE_MSG("InvalidMemoryRegionType for conversion to Pool");
}
}
} // namespace
-KMemoryManager::KMemoryManager(Core::System& system_)
- : system{system_}, pool_locks{
- KLightLock{system_.Kernel()},
- KLightLock{system_.Kernel()},
- KLightLock{system_.Kernel()},
- KLightLock{system_.Kernel()},
- } {}
+KMemoryManager::KMemoryManager(Core::System& system)
+ : m_system{system}, m_memory_layout{system.Kernel().MemoryLayout()},
+ m_pool_locks{
+ KLightLock{system.Kernel()},
+ KLightLock{system.Kernel()},
+ KLightLock{system.Kernel()},
+ KLightLock{system.Kernel()},
+ } {}
void KMemoryManager::Initialize(VAddr management_region, size_t management_region_size) {
// Clear the management region to zero.
const VAddr management_region_end = management_region + management_region_size;
+ // std::memset(GetVoidPointer(management_region), 0, management_region_size);
// Reset our manager count.
- num_managers = 0;
+ m_num_managers = 0;
// Traverse the virtual memory layout tree, initializing each manager as appropriate.
- while (num_managers != MaxManagerCount) {
+ while (m_num_managers != MaxManagerCount) {
// Locate the region that should initialize the current manager.
PAddr region_address = 0;
size_t region_size = 0;
Pool region_pool = Pool::Count;
- for (const auto& it : system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) {
+ for (const auto& it : m_system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) {
// We only care about regions that we need to create managers for.
if (!it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) {
continue;
}
// We want to initialize the managers in order.
- if (it.GetAttributes() != num_managers) {
+ if (it.GetAttributes() != m_num_managers) {
continue;
}
@@ -97,8 +98,8 @@ void KMemoryManager::Initialize(VAddr management_region, size_t management_regio
}
// Initialize a new manager for the region.
- Impl* manager = std::addressof(managers[num_managers++]);
- ASSERT(num_managers <= managers.size());
+ Impl* manager = std::addressof(m_managers[m_num_managers++]);
+ ASSERT(m_num_managers <= m_managers.size());
const size_t cur_size = manager->Initialize(region_address, region_size, management_region,
management_region_end, region_pool);
@@ -107,13 +108,13 @@ void KMemoryManager::Initialize(VAddr management_region, size_t management_regio
// Insert the manager into the pool list.
const auto region_pool_index = static_cast<u32>(region_pool);
- if (pool_managers_tail[region_pool_index] == nullptr) {
- pool_managers_head[region_pool_index] = manager;
+ if (m_pool_managers_tail[region_pool_index] == nullptr) {
+ m_pool_managers_head[region_pool_index] = manager;
} else {
- pool_managers_tail[region_pool_index]->SetNext(manager);
- manager->SetPrev(pool_managers_tail[region_pool_index]);
+ m_pool_managers_tail[region_pool_index]->SetNext(manager);
+ manager->SetPrev(m_pool_managers_tail[region_pool_index]);
}
- pool_managers_tail[region_pool_index] = manager;
+ m_pool_managers_tail[region_pool_index] = manager;
}
// Free each region to its corresponding heap.
@@ -121,11 +122,10 @@ void KMemoryManager::Initialize(VAddr management_region, size_t management_regio
const PAddr ini_start = GetInitialProcessBinaryPhysicalAddress();
const PAddr ini_end = ini_start + InitialProcessBinarySizeMax;
const PAddr ini_last = ini_end - 1;
- for (const auto& it : system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) {
+ for (const auto& it : m_system.Kernel().MemoryLayout().GetPhysicalMemoryRegionTree()) {
if (it.IsDerivedFrom(KMemoryRegionType_DramUserPool)) {
// Get the manager for the region.
- auto index = it.GetAttributes();
- auto& manager = managers[index];
+ auto& manager = m_managers[it.GetAttributes()];
const PAddr cur_start = it.GetAddress();
const PAddr cur_last = it.GetLastAddress();
@@ -162,11 +162,19 @@ void KMemoryManager::Initialize(VAddr management_region, size_t management_regio
}
// Update the used size for all managers.
- for (size_t i = 0; i < num_managers; ++i) {
- managers[i].SetInitialUsedHeapSize(reserved_sizes[i]);
+ for (size_t i = 0; i < m_num_managers; ++i) {
+ m_managers[i].SetInitialUsedHeapSize(reserved_sizes[i]);
}
}
+Result KMemoryManager::InitializeOptimizedMemory(u64 process_id, Pool pool) {
+ UNREACHABLE();
+}
+
+void KMemoryManager::FinalizeOptimizedMemory(u64 process_id, Pool pool) {
+ UNREACHABLE();
+}
+
PAddr KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option) {
// Early return if we're allocating no pages.
if (num_pages == 0) {
@@ -175,7 +183,7 @@ PAddr KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_p
// Lock the pool that we're allocating from.
const auto [pool, dir] = DecodeOption(option);
- KScopedLightLock lk(pool_locks[static_cast<std::size_t>(pool)]);
+ KScopedLightLock lk(m_pool_locks[static_cast<std::size_t>(pool)]);
// Choose a heap based on our page size request.
const s32 heap_index = KPageHeap::GetAlignedBlockIndex(num_pages, align_pages);
@@ -185,7 +193,7 @@ PAddr KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_p
PAddr allocated_block = 0;
for (chosen_manager = this->GetFirstManager(pool, dir); chosen_manager != nullptr;
chosen_manager = this->GetNextManager(chosen_manager, dir)) {
- allocated_block = chosen_manager->AllocateBlock(heap_index, true);
+ allocated_block = chosen_manager->AllocateAligned(heap_index, num_pages, align_pages);
if (allocated_block != 0) {
break;
}
@@ -196,10 +204,9 @@ PAddr KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_p
return 0;
}
- // If we allocated more than we need, free some.
- const size_t allocated_pages = KPageHeap::GetBlockNumPages(heap_index);
- if (allocated_pages > num_pages) {
- chosen_manager->Free(allocated_block + num_pages * PageSize, allocated_pages - num_pages);
+ // Maintain the optimized memory bitmap, if we should.
+ if (m_has_optimized_process[static_cast<size_t>(pool)]) {
+ UNIMPLEMENTED();
}
// Open the first reference to the pages.
@@ -209,20 +216,21 @@ PAddr KMemoryManager::AllocateAndOpenContinuous(size_t num_pages, size_t align_p
}
Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages, Pool pool,
- Direction dir, bool random) {
+ Direction dir, bool unoptimized, bool random) {
// Choose a heap based on our page size request.
const s32 heap_index = KPageHeap::GetBlockIndex(num_pages);
R_UNLESS(0 <= heap_index, ResultOutOfMemory);
// Ensure that we don't leave anything un-freed.
- auto group_guard = SCOPE_GUARD({
+ ON_RESULT_FAILURE {
for (const auto& it : out->Nodes()) {
- auto& manager = this->GetManager(system.Kernel().MemoryLayout(), it.GetAddress());
- const size_t num_pages_to_free =
+ auto& manager = this->GetManager(it.GetAddress());
+ const size_t node_num_pages =
std::min(it.GetNumPages(), (manager.GetEndAddress() - it.GetAddress()) / PageSize);
- manager.Free(it.GetAddress(), num_pages_to_free);
+ manager.Free(it.GetAddress(), node_num_pages);
}
- });
+ out->Finalize();
+ };
// Keep allocating until we've allocated all our pages.
for (s32 index = heap_index; index >= 0 && num_pages > 0; index--) {
@@ -236,12 +244,17 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages,
break;
}
- // Safely add it to our group.
- {
- auto block_guard =
- SCOPE_GUARD({ cur_manager->Free(allocated_block, pages_per_alloc); });
- R_TRY(out->AddBlock(allocated_block, pages_per_alloc));
- block_guard.Cancel();
+ // Ensure we don't leak the block if we fail.
+ ON_RESULT_FAILURE_2 {
+ cur_manager->Free(allocated_block, pages_per_alloc);
+ };
+
+ // Add the block to our group.
+ R_TRY(out->AddBlock(allocated_block, pages_per_alloc));
+
+ // Maintain the optimized memory bitmap, if we should.
+ if (unoptimized) {
+ UNIMPLEMENTED();
}
num_pages -= pages_per_alloc;
@@ -253,8 +266,7 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages,
R_UNLESS(num_pages == 0, ResultOutOfMemory);
// We succeeded!
- group_guard.Cancel();
- return ResultSuccess;
+ R_SUCCEED();
}
Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 option) {
@@ -266,10 +278,11 @@ Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 op
// Lock the pool that we're allocating from.
const auto [pool, dir] = DecodeOption(option);
- KScopedLightLock lk(pool_locks[static_cast<size_t>(pool)]);
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(pool)]);
// Allocate the page group.
- R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, false));
+ R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir,
+ m_has_optimized_process[static_cast<size_t>(pool)], true));
// Open the first reference to the pages.
for (const auto& block : out->Nodes()) {
@@ -277,7 +290,7 @@ Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 op
size_t remaining_pages = block.GetNumPages();
while (remaining_pages > 0) {
// Get the manager for the current address.
- auto& manager = this->GetManager(system.Kernel().MemoryLayout(), cur_address);
+ auto& manager = this->GetManager(cur_address);
// Process part or all of the block.
const size_t cur_pages =
@@ -290,11 +303,11 @@ Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 op
}
}
- return ResultSuccess;
+ R_SUCCEED();
}
-Result KMemoryManager::AllocateAndOpenForProcess(KPageGroup* out, size_t num_pages, u32 option,
- u64 process_id, u8 fill_pattern) {
+Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32 option,
+ u64 process_id, u8 fill_pattern) {
ASSERT(out != nullptr);
ASSERT(out->GetNumPages() == 0);
@@ -302,83 +315,89 @@ Result KMemoryManager::AllocateAndOpenForProcess(KPageGroup* out, size_t num_pag
const auto [pool, dir] = DecodeOption(option);
// Allocate the memory.
+ bool optimized;
{
// Lock the pool that we're allocating from.
- KScopedLightLock lk(pool_locks[static_cast<size_t>(pool)]);
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(pool)]);
+
+ // Check if we have an optimized process.
+ const bool has_optimized = m_has_optimized_process[static_cast<size_t>(pool)];
+ const bool is_optimized = m_optimized_process_ids[static_cast<size_t>(pool)] == process_id;
// Allocate the page group.
- R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, false));
+ R_TRY(this->AllocatePageGroupImpl(out, num_pages, pool, dir, has_optimized && !is_optimized,
+ false));
- // Open the first reference to the pages.
- for (const auto& block : out->Nodes()) {
- PAddr cur_address = block.GetAddress();
- size_t remaining_pages = block.GetNumPages();
- while (remaining_pages > 0) {
- // Get the manager for the current address.
- auto& manager = this->GetManager(system.Kernel().MemoryLayout(), cur_address);
-
- // Process part or all of the block.
- const size_t cur_pages =
- std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
- manager.OpenFirst(cur_address, cur_pages);
-
- // Advance.
- cur_address += cur_pages * PageSize;
- remaining_pages -= cur_pages;
- }
- }
+ // Set whether we should optimize.
+ optimized = has_optimized && is_optimized;
}
- // Set all the allocated memory.
- for (const auto& block : out->Nodes()) {
- std::memset(system.DeviceMemory().GetPointer<void>(block.GetAddress()), fill_pattern,
- block.GetSize());
- }
+ // Perform optimized memory tracking, if we should.
+ if (optimized) {
+ // Iterate over the allocated blocks.
+ for (const auto& block : out->Nodes()) {
+ // Get the block extents.
+ const PAddr block_address = block.GetAddress();
+ const size_t block_pages = block.GetNumPages();
- return ResultSuccess;
-}
+ // If it has no pages, we don't need to do anything.
+ if (block_pages == 0) {
+ continue;
+ }
-void KMemoryManager::Open(PAddr address, size_t num_pages) {
- // Repeatedly open references until we've done so for all pages.
- while (num_pages) {
- auto& manager = this->GetManager(system.Kernel().MemoryLayout(), address);
- const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
+ // Fill all the pages that we need to fill.
+ bool any_new = false;
+ {
+ PAddr cur_address = block_address;
+ size_t remaining_pages = block_pages;
+ while (remaining_pages > 0) {
+ // Get the manager for the current address.
+ auto& manager = this->GetManager(cur_address);
+
+ // Process part or all of the block.
+ const size_t cur_pages =
+ std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
+ any_new =
+ manager.ProcessOptimizedAllocation(cur_address, cur_pages, fill_pattern);
+
+ // Advance.
+ cur_address += cur_pages * PageSize;
+ remaining_pages -= cur_pages;
+ }
+ }
- {
- KScopedLightLock lk(pool_locks[static_cast<size_t>(manager.GetPool())]);
- manager.Open(address, cur_pages);
+ // If there are new pages, update tracking for the allocation.
+ if (any_new) {
+ // Update tracking for the allocation.
+ PAddr cur_address = block_address;
+ size_t remaining_pages = block_pages;
+ while (remaining_pages > 0) {
+ // Get the manager for the current address.
+ auto& manager = this->GetManager(cur_address);
+
+ // Lock the pool for the manager.
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(manager.GetPool())]);
+
+ // Track some or all of the current pages.
+ const size_t cur_pages =
+ std::min(remaining_pages, manager.GetPageOffsetToEnd(cur_address));
+ manager.TrackOptimizedAllocation(cur_address, cur_pages);
+
+ // Advance.
+ cur_address += cur_pages * PageSize;
+ remaining_pages -= cur_pages;
+ }
+ }
}
-
- num_pages -= cur_pages;
- address += cur_pages * PageSize;
- }
-}
-
-void KMemoryManager::Close(PAddr address, size_t num_pages) {
- // Repeatedly close references until we've done so for all pages.
- while (num_pages) {
- auto& manager = this->GetManager(system.Kernel().MemoryLayout(), address);
- const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
-
- {
- KScopedLightLock lk(pool_locks[static_cast<size_t>(manager.GetPool())]);
- manager.Close(address, cur_pages);
+ } else {
+ // Set all the allocated memory.
+ for (const auto& block : out->Nodes()) {
+ std::memset(m_system.DeviceMemory().GetPointer<void>(block.GetAddress()), fill_pattern,
+ block.GetSize());
}
-
- num_pages -= cur_pages;
- address += cur_pages * PageSize;
}
-}
-void KMemoryManager::Close(const KPageGroup& pg) {
- for (const auto& node : pg.Nodes()) {
- Close(node.GetAddress(), node.GetNumPages());
- }
-}
-void KMemoryManager::Open(const KPageGroup& pg) {
- for (const auto& node : pg.Nodes()) {
- Open(node.GetAddress(), node.GetNumPages());
- }
+ R_SUCCEED();
}
size_t KMemoryManager::Impl::Initialize(PAddr address, size_t size, VAddr management,
@@ -394,18 +413,31 @@ size_t KMemoryManager::Impl::Initialize(PAddr address, size_t size, VAddr manage
ASSERT(Common::IsAligned(total_management_size, PageSize));
// Setup region.
- pool = p;
- management_region = management;
- page_reference_counts.resize(
+ m_pool = p;
+ m_management_region = management;
+ m_page_reference_counts.resize(
Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize() / PageSize);
- ASSERT(Common::IsAligned(management_region, PageSize));
+ ASSERT(Common::IsAligned(m_management_region, PageSize));
// Initialize the manager's KPageHeap.
- heap.Initialize(address, size, management + manager_size, page_heap_size);
+ m_heap.Initialize(address, size, management + manager_size, page_heap_size);
return total_management_size;
}
+void KMemoryManager::Impl::TrackUnoptimizedAllocation(PAddr block, size_t num_pages) {
+ UNREACHABLE();
+}
+
+void KMemoryManager::Impl::TrackOptimizedAllocation(PAddr block, size_t num_pages) {
+ UNREACHABLE();
+}
+
+bool KMemoryManager::Impl::ProcessOptimizedAllocation(PAddr block, size_t num_pages,
+ u8 fill_pattern) {
+ UNREACHABLE();
+}
+
size_t KMemoryManager::Impl::CalculateManagementOverheadSize(size_t region_size) {
const size_t ref_count_size = (region_size / PageSize) * sizeof(u16);
const size_t optimize_map_size =
diff --git a/src/core/hle/kernel/k_memory_manager.h b/src/core/hle/kernel/k_memory_manager.h
index dcb9b6348..401d4e644 100644
--- a/src/core/hle/kernel/k_memory_manager.h
+++ b/src/core/hle/kernel/k_memory_manager.h
@@ -21,11 +21,8 @@ namespace Kernel {
class KPageGroup;
-class KMemoryManager final {
+class KMemoryManager {
public:
- YUZU_NON_COPYABLE(KMemoryManager);
- YUZU_NON_MOVEABLE(KMemoryManager);
-
enum class Pool : u32 {
Application = 0,
Applet = 1,
@@ -45,16 +42,85 @@ public:
enum class Direction : u32 {
FromFront = 0,
FromBack = 1,
-
Shift = 0,
Mask = (0xF << Shift),
};
- explicit KMemoryManager(Core::System& system_);
+ static constexpr size_t MaxManagerCount = 10;
+
+ explicit KMemoryManager(Core::System& system);
void Initialize(VAddr management_region, size_t management_region_size);
- constexpr size_t GetSize(Pool pool) const {
+ Result InitializeOptimizedMemory(u64 process_id, Pool pool);
+ void FinalizeOptimizedMemory(u64 process_id, Pool pool);
+
+ PAddr AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option);
+ Result AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 option);
+ Result AllocateForProcess(KPageGroup* out, size_t num_pages, u32 option, u64 process_id,
+ u8 fill_pattern);
+
+ Pool GetPool(PAddr address) const {
+ return this->GetManager(address).GetPool();
+ }
+
+ void Open(PAddr address, size_t num_pages) {
+ // Repeatedly open references until we've done so for all pages.
+ while (num_pages) {
+ auto& manager = this->GetManager(address);
+ const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
+
+ {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(manager.GetPool())]);
+ manager.Open(address, cur_pages);
+ }
+
+ num_pages -= cur_pages;
+ address += cur_pages * PageSize;
+ }
+ }
+
+ void OpenFirst(PAddr address, size_t num_pages) {
+ // Repeatedly open references until we've done so for all pages.
+ while (num_pages) {
+ auto& manager = this->GetManager(address);
+ const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
+
+ {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(manager.GetPool())]);
+ manager.OpenFirst(address, cur_pages);
+ }
+
+ num_pages -= cur_pages;
+ address += cur_pages * PageSize;
+ }
+ }
+
+ void Close(PAddr address, size_t num_pages) {
+ // Repeatedly close references until we've done so for all pages.
+ while (num_pages) {
+ auto& manager = this->GetManager(address);
+ const size_t cur_pages = std::min(num_pages, manager.GetPageOffsetToEnd(address));
+
+ {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(manager.GetPool())]);
+ manager.Close(address, cur_pages);
+ }
+
+ num_pages -= cur_pages;
+ address += cur_pages * PageSize;
+ }
+ }
+
+ size_t GetSize() {
+ size_t total = 0;
+ for (size_t i = 0; i < m_num_managers; i++) {
+ total += m_managers[i].GetSize();
+ }
+ return total;
+ }
+
+ size_t GetSize(Pool pool) {
constexpr Direction GetSizeDirection = Direction::FromFront;
size_t total = 0;
for (auto* manager = this->GetFirstManager(pool, GetSizeDirection); manager != nullptr;
@@ -64,18 +130,36 @@ public:
return total;
}
- PAddr AllocateAndOpenContinuous(size_t num_pages, size_t align_pages, u32 option);
- Result AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 option);
- Result AllocateAndOpenForProcess(KPageGroup* out, size_t num_pages, u32 option, u64 process_id,
- u8 fill_pattern);
+ size_t GetFreeSize() {
+ size_t total = 0;
+ for (size_t i = 0; i < m_num_managers; i++) {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(m_managers[i].GetPool())]);
+ total += m_managers[i].GetFreeSize();
+ }
+ return total;
+ }
- static constexpr size_t MaxManagerCount = 10;
+ size_t GetFreeSize(Pool pool) {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(pool)]);
+
+ constexpr Direction GetSizeDirection = Direction::FromFront;
+ size_t total = 0;
+ for (auto* manager = this->GetFirstManager(pool, GetSizeDirection); manager != nullptr;
+ manager = this->GetNextManager(manager, GetSizeDirection)) {
+ total += manager->GetFreeSize();
+ }
+ return total;
+ }
- void Close(PAddr address, size_t num_pages);
- void Close(const KPageGroup& pg);
+ void DumpFreeList(Pool pool) {
+ KScopedLightLock lk(m_pool_locks[static_cast<size_t>(pool)]);
- void Open(PAddr address, size_t num_pages);
- void Open(const KPageGroup& pg);
+ constexpr Direction DumpDirection = Direction::FromFront;
+ for (auto* manager = this->GetFirstManager(pool, DumpDirection); manager != nullptr;
+ manager = this->GetNextManager(manager, DumpDirection)) {
+ manager->DumpFreeList();
+ }
+ }
public:
static size_t CalculateManagementOverheadSize(size_t region_size) {
@@ -88,14 +172,13 @@ public:
}
static constexpr Pool GetPool(u32 option) {
- return static_cast<Pool>((static_cast<u32>(option) & static_cast<u32>(Pool::Mask)) >>
+ return static_cast<Pool>((option & static_cast<u32>(Pool::Mask)) >>
static_cast<u32>(Pool::Shift));
}
static constexpr Direction GetDirection(u32 option) {
- return static_cast<Direction>(
- (static_cast<u32>(option) & static_cast<u32>(Direction::Mask)) >>
- static_cast<u32>(Direction::Shift));
+ return static_cast<Direction>((option & static_cast<u32>(Direction::Mask)) >>
+ static_cast<u32>(Direction::Shift));
}
static constexpr std::tuple<Pool, Direction> DecodeOption(u32 option) {
@@ -103,74 +186,88 @@ public:
}
private:
- class Impl final {
+ class Impl {
public:
- YUZU_NON_COPYABLE(Impl);
- YUZU_NON_MOVEABLE(Impl);
+ static size_t CalculateManagementOverheadSize(size_t region_size);
+
+ static constexpr size_t CalculateOptimizedProcessOverheadSize(size_t region_size) {
+ return (Common::AlignUp((region_size / PageSize), Common::BitSize<u64>()) /
+ Common::BitSize<u64>()) *
+ sizeof(u64);
+ }
+ public:
Impl() = default;
- ~Impl() = default;
size_t Initialize(PAddr address, size_t size, VAddr management, VAddr management_end,
Pool p);
- VAddr AllocateBlock(s32 index, bool random) {
- return heap.AllocateBlock(index, random);
+ PAddr AllocateBlock(s32 index, bool random) {
+ return m_heap.AllocateBlock(index, random);
}
-
- void Free(VAddr addr, size_t num_pages) {
- heap.Free(addr, num_pages);
+ PAddr AllocateAligned(s32 index, size_t num_pages, size_t align_pages) {
+ return m_heap.AllocateAligned(index, num_pages, align_pages);
+ }
+ void Free(PAddr addr, size_t num_pages) {
+ m_heap.Free(addr, num_pages);
}
void SetInitialUsedHeapSize(size_t reserved_size) {
- heap.SetInitialUsedSize(reserved_size);
+ m_heap.SetInitialUsedSize(reserved_size);
}
- constexpr Pool GetPool() const {
- return pool;
+ void InitializeOptimizedMemory() {
+ UNIMPLEMENTED();
}
+ void TrackUnoptimizedAllocation(PAddr block, size_t num_pages);
+ void TrackOptimizedAllocation(PAddr block, size_t num_pages);
+
+ bool ProcessOptimizedAllocation(PAddr block, size_t num_pages, u8 fill_pattern);
+
+ constexpr Pool GetPool() const {
+ return m_pool;
+ }
constexpr size_t GetSize() const {
- return heap.GetSize();
+ return m_heap.GetSize();
+ }
+ constexpr PAddr GetEndAddress() const {
+ return m_heap.GetEndAddress();
}
- constexpr VAddr GetAddress() const {
- return heap.GetAddress();
+ size_t GetFreeSize() const {
+ return m_heap.GetFreeSize();
}
- constexpr VAddr GetEndAddress() const {
- return heap.GetEndAddress();
+ void DumpFreeList() const {
+ UNIMPLEMENTED();
}
constexpr size_t GetPageOffset(PAddr address) const {
- return heap.GetPageOffset(address);
+ return m_heap.GetPageOffset(address);
}
-
constexpr size_t GetPageOffsetToEnd(PAddr address) const {
- return heap.GetPageOffsetToEnd(address);
+ return m_heap.GetPageOffsetToEnd(address);
}
constexpr void SetNext(Impl* n) {
- next = n;
+ m_next = n;
}
-
constexpr void SetPrev(Impl* n) {
- prev = n;
+ m_prev = n;
}
-
constexpr Impl* GetNext() const {
- return next;
+ return m_next;
}
-
constexpr Impl* GetPrev() const {
- return prev;
+ return m_prev;
}
void OpenFirst(PAddr address, size_t num_pages) {
size_t index = this->GetPageOffset(address);
const size_t end = index + num_pages;
while (index < end) {
- const RefCount ref_count = (++page_reference_counts[index]);
+ const RefCount ref_count = (++m_page_reference_counts[index]);
ASSERT(ref_count == 1);
index++;
@@ -181,7 +278,7 @@ private:
size_t index = this->GetPageOffset(address);
const size_t end = index + num_pages;
while (index < end) {
- const RefCount ref_count = (++page_reference_counts[index]);
+ const RefCount ref_count = (++m_page_reference_counts[index]);
ASSERT(ref_count > 1);
index++;
@@ -195,8 +292,8 @@ private:
size_t free_start = 0;
size_t free_count = 0;
while (index < end) {
- ASSERT(page_reference_counts[index] > 0);
- const RefCount ref_count = (--page_reference_counts[index]);
+ ASSERT(m_page_reference_counts[index] > 0);
+ const RefCount ref_count = (--m_page_reference_counts[index]);
// Keep track of how many zero refcounts we see in a row, to minimize calls to free.
if (ref_count == 0) {
@@ -208,7 +305,7 @@ private:
}
} else {
if (free_count > 0) {
- this->Free(heap.GetAddress() + free_start * PageSize, free_count);
+ this->Free(m_heap.GetAddress() + free_start * PageSize, free_count);
free_count = 0;
}
}
@@ -217,44 +314,36 @@ private:
}
if (free_count > 0) {
- this->Free(heap.GetAddress() + free_start * PageSize, free_count);
+ this->Free(m_heap.GetAddress() + free_start * PageSize, free_count);
}
}
- static size_t CalculateManagementOverheadSize(size_t region_size);
-
- static constexpr size_t CalculateOptimizedProcessOverheadSize(size_t region_size) {
- return (Common::AlignUp((region_size / PageSize), Common::BitSize<u64>()) /
- Common::BitSize<u64>()) *
- sizeof(u64);
- }
-
private:
using RefCount = u16;
- KPageHeap heap;
- std::vector<RefCount> page_reference_counts;
- VAddr management_region{};
- Pool pool{};
- Impl* next{};
- Impl* prev{};
+ KPageHeap m_heap;
+ std::vector<RefCount> m_page_reference_counts;
+ VAddr m_management_region{};
+ Pool m_pool{};
+ Impl* m_next{};
+ Impl* m_prev{};
};
private:
- Impl& GetManager(const KMemoryLayout& memory_layout, PAddr address) {
- return managers[memory_layout.GetPhysicalLinearRegion(address).GetAttributes()];
+ Impl& GetManager(PAddr address) {
+ return m_managers[m_memory_layout.GetPhysicalLinearRegion(address).GetAttributes()];
}
- const Impl& GetManager(const KMemoryLayout& memory_layout, PAddr address) const {
- return managers[memory_layout.GetPhysicalLinearRegion(address).GetAttributes()];
+ const Impl& GetManager(PAddr address) const {
+ return m_managers[m_memory_layout.GetPhysicalLinearRegion(address).GetAttributes()];
}
- constexpr Impl* GetFirstManager(Pool pool, Direction dir) const {
- return dir == Direction::FromBack ? pool_managers_tail[static_cast<size_t>(pool)]
- : pool_managers_head[static_cast<size_t>(pool)];
+ constexpr Impl* GetFirstManager(Pool pool, Direction dir) {
+ return dir == Direction::FromBack ? m_pool_managers_tail[static_cast<size_t>(pool)]
+ : m_pool_managers_head[static_cast<size_t>(pool)];
}
- constexpr Impl* GetNextManager(Impl* cur, Direction dir) const {
+ constexpr Impl* GetNextManager(Impl* cur, Direction dir) {
if (dir == Direction::FromBack) {
return cur->GetPrev();
} else {
@@ -263,15 +352,21 @@ private:
}
Result AllocatePageGroupImpl(KPageGroup* out, size_t num_pages, Pool pool, Direction dir,
- bool random);
+ bool unoptimized, bool random);
private:
- Core::System& system;
- std::array<KLightLock, static_cast<size_t>(Pool::Count)> pool_locks;
- std::array<Impl*, MaxManagerCount> pool_managers_head{};
- std::array<Impl*, MaxManagerCount> pool_managers_tail{};
- std::array<Impl, MaxManagerCount> managers;
- size_t num_managers{};
+ template <typename T>
+ using PoolArray = std::array<T, static_cast<size_t>(Pool::Count)>;
+
+ Core::System& m_system;
+ const KMemoryLayout& m_memory_layout;
+ PoolArray<KLightLock> m_pool_locks;
+ std::array<Impl*, MaxManagerCount> m_pool_managers_head{};
+ std::array<Impl*, MaxManagerCount> m_pool_managers_tail{};
+ std::array<Impl, MaxManagerCount> m_managers;
+ size_t m_num_managers{};
+ PoolArray<u64> m_optimized_process_ids{};
+ PoolArray<bool> m_has_optimized_process{};
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_memory_region_type.h b/src/core/hle/kernel/k_memory_region_type.h
index 7e2fcccdc..e5630c1ac 100644
--- a/src/core/hle/kernel/k_memory_region_type.h
+++ b/src/core/hle/kernel/k_memory_region_type.h
@@ -142,32 +142,38 @@ private:
} // namespace impl
-constexpr auto KMemoryRegionType_None = impl::KMemoryRegionTypeValue();
-constexpr auto KMemoryRegionType_Kernel = KMemoryRegionType_None.DeriveInitial(0, 2);
-constexpr auto KMemoryRegionType_Dram = KMemoryRegionType_None.DeriveInitial(1, 2);
+constexpr inline auto KMemoryRegionType_None = impl::KMemoryRegionTypeValue();
+
+constexpr inline auto KMemoryRegionType_Kernel = KMemoryRegionType_None.DeriveInitial(0, 2);
+constexpr inline auto KMemoryRegionType_Dram = KMemoryRegionType_None.DeriveInitial(1, 2);
static_assert(KMemoryRegionType_Kernel.GetValue() == 0x1);
static_assert(KMemoryRegionType_Dram.GetValue() == 0x2);
-constexpr auto KMemoryRegionType_DramKernelBase =
+// constexpr inline auto KMemoryRegionType_CoreLocalRegion =
+// KMemoryRegionType_None.DeriveInitial(2).Finalize();
+// static_assert(KMemoryRegionType_CoreLocalRegion.GetValue() == 0x4);
+
+constexpr inline auto KMemoryRegionType_DramKernelBase =
KMemoryRegionType_Dram.DeriveSparse(0, 3, 0)
.SetAttribute(KMemoryRegionAttr_NoUserMap)
.SetAttribute(KMemoryRegionAttr_CarveoutProtected);
-constexpr auto KMemoryRegionType_DramReservedBase = KMemoryRegionType_Dram.DeriveSparse(0, 3, 1);
-constexpr auto KMemoryRegionType_DramHeapBase =
+constexpr inline auto KMemoryRegionType_DramReservedBase =
+ KMemoryRegionType_Dram.DeriveSparse(0, 3, 1);
+constexpr inline auto KMemoryRegionType_DramHeapBase =
KMemoryRegionType_Dram.DeriveSparse(0, 3, 2).SetAttribute(KMemoryRegionAttr_LinearMapped);
static_assert(KMemoryRegionType_DramKernelBase.GetValue() ==
(0xE | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap));
static_assert(KMemoryRegionType_DramReservedBase.GetValue() == (0x16));
static_assert(KMemoryRegionType_DramHeapBase.GetValue() == (0x26 | KMemoryRegionAttr_LinearMapped));
-constexpr auto KMemoryRegionType_DramKernelCode =
+constexpr inline auto KMemoryRegionType_DramKernelCode =
KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 0);
-constexpr auto KMemoryRegionType_DramKernelSlab =
+constexpr inline auto KMemoryRegionType_DramKernelSlab =
KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 1);
-constexpr auto KMemoryRegionType_DramKernelPtHeap =
+constexpr inline auto KMemoryRegionType_DramKernelPtHeap =
KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 2).SetAttribute(
KMemoryRegionAttr_LinearMapped);
-constexpr auto KMemoryRegionType_DramKernelInitPt =
+constexpr inline auto KMemoryRegionType_DramKernelInitPt =
KMemoryRegionType_DramKernelBase.DeriveSparse(0, 4, 3).SetAttribute(
KMemoryRegionAttr_LinearMapped);
static_assert(KMemoryRegionType_DramKernelCode.GetValue() ==
@@ -181,32 +187,40 @@ static_assert(KMemoryRegionType_DramKernelInitPt.GetValue() ==
(0x44E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap |
KMemoryRegionAttr_LinearMapped));
-constexpr auto KMemoryRegionType_DramReservedEarly =
+constexpr inline auto KMemoryRegionType_DramKernelSecureAppletMemory =
+ KMemoryRegionType_DramKernelBase.DeriveSparse(1, 3, 0).SetAttribute(
+ KMemoryRegionAttr_LinearMapped);
+static_assert(KMemoryRegionType_DramKernelSecureAppletMemory.GetValue() ==
+ (0x18E | KMemoryRegionAttr_CarveoutProtected | KMemoryRegionAttr_NoUserMap |
+ KMemoryRegionAttr_LinearMapped));
+
+constexpr inline auto KMemoryRegionType_DramReservedEarly =
KMemoryRegionType_DramReservedBase.DeriveAttribute(KMemoryRegionAttr_NoUserMap);
static_assert(KMemoryRegionType_DramReservedEarly.GetValue() ==
(0x16 | KMemoryRegionAttr_NoUserMap));
-constexpr auto KMemoryRegionType_KernelTraceBuffer =
+constexpr inline auto KMemoryRegionType_KernelTraceBuffer =
KMemoryRegionType_DramReservedBase.DeriveSparse(0, 3, 0)
.SetAttribute(KMemoryRegionAttr_LinearMapped)
.SetAttribute(KMemoryRegionAttr_UserReadOnly);
-constexpr auto KMemoryRegionType_OnMemoryBootImage =
+constexpr inline auto KMemoryRegionType_OnMemoryBootImage =
KMemoryRegionType_DramReservedBase.DeriveSparse(0, 3, 1);
-constexpr auto KMemoryRegionType_DTB = KMemoryRegionType_DramReservedBase.DeriveSparse(0, 3, 2);
+constexpr inline auto KMemoryRegionType_DTB =
+ KMemoryRegionType_DramReservedBase.DeriveSparse(0, 3, 2);
static_assert(KMemoryRegionType_KernelTraceBuffer.GetValue() ==
(0xD6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_UserReadOnly));
static_assert(KMemoryRegionType_OnMemoryBootImage.GetValue() == 0x156);
static_assert(KMemoryRegionType_DTB.GetValue() == 0x256);
-constexpr auto KMemoryRegionType_DramPoolPartition =
+constexpr inline auto KMemoryRegionType_DramPoolPartition =
KMemoryRegionType_DramHeapBase.DeriveAttribute(KMemoryRegionAttr_NoUserMap);
static_assert(KMemoryRegionType_DramPoolPartition.GetValue() ==
(0x26 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap));
-constexpr auto KMemoryRegionType_DramPoolManagement =
+constexpr inline auto KMemoryRegionType_DramPoolManagement =
KMemoryRegionType_DramPoolPartition.DeriveTransition(0, 2).DeriveTransition().SetAttribute(
KMemoryRegionAttr_CarveoutProtected);
-constexpr auto KMemoryRegionType_DramUserPool =
+constexpr inline auto KMemoryRegionType_DramUserPool =
KMemoryRegionType_DramPoolPartition.DeriveTransition(1, 2).DeriveTransition();
static_assert(KMemoryRegionType_DramPoolManagement.GetValue() ==
(0x166 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap |
@@ -214,11 +228,13 @@ static_assert(KMemoryRegionType_DramPoolManagement.GetValue() ==
static_assert(KMemoryRegionType_DramUserPool.GetValue() ==
(0x1A6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap));
-constexpr auto KMemoryRegionType_DramApplicationPool = KMemoryRegionType_DramUserPool.Derive(4, 0);
-constexpr auto KMemoryRegionType_DramAppletPool = KMemoryRegionType_DramUserPool.Derive(4, 1);
-constexpr auto KMemoryRegionType_DramSystemNonSecurePool =
+constexpr inline auto KMemoryRegionType_DramApplicationPool =
+ KMemoryRegionType_DramUserPool.Derive(4, 0);
+constexpr inline auto KMemoryRegionType_DramAppletPool =
+ KMemoryRegionType_DramUserPool.Derive(4, 1);
+constexpr inline auto KMemoryRegionType_DramSystemNonSecurePool =
KMemoryRegionType_DramUserPool.Derive(4, 2);
-constexpr auto KMemoryRegionType_DramSystemPool =
+constexpr inline auto KMemoryRegionType_DramSystemPool =
KMemoryRegionType_DramUserPool.Derive(4, 3).SetAttribute(KMemoryRegionAttr_CarveoutProtected);
static_assert(KMemoryRegionType_DramApplicationPool.GetValue() ==
(0x7A6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap));
@@ -230,50 +246,55 @@ static_assert(KMemoryRegionType_DramSystemPool.GetValue() ==
(0x13A6 | KMemoryRegionAttr_LinearMapped | KMemoryRegionAttr_NoUserMap |
KMemoryRegionAttr_CarveoutProtected));
-constexpr auto KMemoryRegionType_VirtualDramHeapBase = KMemoryRegionType_Dram.DeriveSparse(1, 3, 0);
-constexpr auto KMemoryRegionType_VirtualDramKernelPtHeap =
+constexpr inline auto KMemoryRegionType_VirtualDramHeapBase =
+ KMemoryRegionType_Dram.DeriveSparse(1, 3, 0);
+constexpr inline auto KMemoryRegionType_VirtualDramKernelPtHeap =
KMemoryRegionType_Dram.DeriveSparse(1, 3, 1);
-constexpr auto KMemoryRegionType_VirtualDramKernelTraceBuffer =
+constexpr inline auto KMemoryRegionType_VirtualDramKernelTraceBuffer =
KMemoryRegionType_Dram.DeriveSparse(1, 3, 2);
static_assert(KMemoryRegionType_VirtualDramHeapBase.GetValue() == 0x1A);
static_assert(KMemoryRegionType_VirtualDramKernelPtHeap.GetValue() == 0x2A);
static_assert(KMemoryRegionType_VirtualDramKernelTraceBuffer.GetValue() == 0x4A);
// UNUSED: .DeriveSparse(2, 2, 0);
-constexpr auto KMemoryRegionType_VirtualDramUnknownDebug =
+constexpr inline auto KMemoryRegionType_VirtualDramUnknownDebug =
KMemoryRegionType_Dram.DeriveSparse(2, 2, 1);
static_assert(KMemoryRegionType_VirtualDramUnknownDebug.GetValue() == (0x52));
-constexpr auto KMemoryRegionType_VirtualDramKernelInitPt =
+constexpr inline auto KMemoryRegionType_VirtualDramKernelSecureAppletMemory =
+ KMemoryRegionType_Dram.DeriveSparse(3, 1, 0);
+static_assert(KMemoryRegionType_VirtualDramKernelSecureAppletMemory.GetValue() == (0x62));
+
+constexpr inline auto KMemoryRegionType_VirtualDramKernelInitPt =
KMemoryRegionType_VirtualDramHeapBase.Derive(3, 0);
-constexpr auto KMemoryRegionType_VirtualDramPoolManagement =
+constexpr inline auto KMemoryRegionType_VirtualDramPoolManagement =
KMemoryRegionType_VirtualDramHeapBase.Derive(3, 1);
-constexpr auto KMemoryRegionType_VirtualDramUserPool =
+constexpr inline auto KMemoryRegionType_VirtualDramUserPool =
KMemoryRegionType_VirtualDramHeapBase.Derive(3, 2);
static_assert(KMemoryRegionType_VirtualDramKernelInitPt.GetValue() == 0x19A);
static_assert(KMemoryRegionType_VirtualDramPoolManagement.GetValue() == 0x29A);
static_assert(KMemoryRegionType_VirtualDramUserPool.GetValue() == 0x31A);
-// NOTE: For unknown reason, the pools are derived out-of-order here. It's worth eventually trying
-// to understand why Nintendo made this choice.
+// NOTE: For unknown reason, the pools are derived out-of-order here.
+// It's worth eventually trying to understand why Nintendo made this choice.
// UNUSED: .Derive(6, 0);
// UNUSED: .Derive(6, 1);
-constexpr auto KMemoryRegionType_VirtualDramAppletPool =
+constexpr inline auto KMemoryRegionType_VirtualDramAppletPool =
KMemoryRegionType_VirtualDramUserPool.Derive(6, 2);
-constexpr auto KMemoryRegionType_VirtualDramApplicationPool =
+constexpr inline auto KMemoryRegionType_VirtualDramApplicationPool =
KMemoryRegionType_VirtualDramUserPool.Derive(6, 3);
-constexpr auto KMemoryRegionType_VirtualDramSystemNonSecurePool =
+constexpr inline auto KMemoryRegionType_VirtualDramSystemNonSecurePool =
KMemoryRegionType_VirtualDramUserPool.Derive(6, 4);
-constexpr auto KMemoryRegionType_VirtualDramSystemPool =
+constexpr inline auto KMemoryRegionType_VirtualDramSystemPool =
KMemoryRegionType_VirtualDramUserPool.Derive(6, 5);
static_assert(KMemoryRegionType_VirtualDramAppletPool.GetValue() == 0x1B1A);
static_assert(KMemoryRegionType_VirtualDramApplicationPool.GetValue() == 0x271A);
static_assert(KMemoryRegionType_VirtualDramSystemNonSecurePool.GetValue() == 0x2B1A);
static_assert(KMemoryRegionType_VirtualDramSystemPool.GetValue() == 0x331A);
-constexpr auto KMemoryRegionType_ArchDeviceBase =
+constexpr inline auto KMemoryRegionType_ArchDeviceBase =
KMemoryRegionType_Kernel.DeriveTransition(0, 1).SetSparseOnly();
-constexpr auto KMemoryRegionType_BoardDeviceBase =
+constexpr inline auto KMemoryRegionType_BoardDeviceBase =
KMemoryRegionType_Kernel.DeriveTransition(0, 2).SetDenseOnly();
static_assert(KMemoryRegionType_ArchDeviceBase.GetValue() == 0x5);
static_assert(KMemoryRegionType_BoardDeviceBase.GetValue() == 0x5);
@@ -284,7 +305,7 @@ static_assert(KMemoryRegionType_BoardDeviceBase.GetValue() == 0x5);
#error "Unimplemented"
#else
// Default to no architecture devices.
-constexpr auto NumArchitectureDeviceRegions = 0;
+constexpr inline auto NumArchitectureDeviceRegions = 0;
#endif
static_assert(NumArchitectureDeviceRegions >= 0);
@@ -292,34 +313,35 @@ static_assert(NumArchitectureDeviceRegions >= 0);
#include "core/hle/kernel/board/nintendo/nx/k_memory_region_device_types.inc"
#else
// Default to no board devices.
-constexpr auto NumBoardDeviceRegions = 0;
+constexpr inline auto NumBoardDeviceRegions = 0;
#endif
static_assert(NumBoardDeviceRegions >= 0);
-constexpr auto KMemoryRegionType_KernelCode = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 0);
-constexpr auto KMemoryRegionType_KernelStack = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 1);
-constexpr auto KMemoryRegionType_KernelMisc = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 2);
-constexpr auto KMemoryRegionType_KernelSlab = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 3);
+constexpr inline auto KMemoryRegionType_KernelCode = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 0);
+constexpr inline auto KMemoryRegionType_KernelStack =
+ KMemoryRegionType_Kernel.DeriveSparse(1, 4, 1);
+constexpr inline auto KMemoryRegionType_KernelMisc = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 2);
+constexpr inline auto KMemoryRegionType_KernelSlab = KMemoryRegionType_Kernel.DeriveSparse(1, 4, 3);
static_assert(KMemoryRegionType_KernelCode.GetValue() == 0x19);
static_assert(KMemoryRegionType_KernelStack.GetValue() == 0x29);
static_assert(KMemoryRegionType_KernelMisc.GetValue() == 0x49);
static_assert(KMemoryRegionType_KernelSlab.GetValue() == 0x89);
-constexpr auto KMemoryRegionType_KernelMiscDerivedBase =
+constexpr inline auto KMemoryRegionType_KernelMiscDerivedBase =
KMemoryRegionType_KernelMisc.DeriveTransition();
static_assert(KMemoryRegionType_KernelMiscDerivedBase.GetValue() == 0x149);
// UNUSED: .Derive(7, 0);
-constexpr auto KMemoryRegionType_KernelMiscMainStack =
+constexpr inline auto KMemoryRegionType_KernelMiscMainStack =
KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 1);
-constexpr auto KMemoryRegionType_KernelMiscMappedDevice =
+constexpr inline auto KMemoryRegionType_KernelMiscMappedDevice =
KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 2);
-constexpr auto KMemoryRegionType_KernelMiscExceptionStack =
+constexpr inline auto KMemoryRegionType_KernelMiscExceptionStack =
KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 3);
-constexpr auto KMemoryRegionType_KernelMiscUnknownDebug =
+constexpr inline auto KMemoryRegionType_KernelMiscUnknownDebug =
KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 4);
// UNUSED: .Derive(7, 5);
-constexpr auto KMemoryRegionType_KernelMiscIdleStack =
+constexpr inline auto KMemoryRegionType_KernelMiscIdleStack =
KMemoryRegionType_KernelMiscDerivedBase.Derive(7, 6);
static_assert(KMemoryRegionType_KernelMiscMainStack.GetValue() == 0xB49);
static_assert(KMemoryRegionType_KernelMiscMappedDevice.GetValue() == 0xD49);
@@ -327,7 +349,8 @@ static_assert(KMemoryRegionType_KernelMiscExceptionStack.GetValue() == 0x1349);
static_assert(KMemoryRegionType_KernelMiscUnknownDebug.GetValue() == 0x1549);
static_assert(KMemoryRegionType_KernelMiscIdleStack.GetValue() == 0x2349);
-constexpr auto KMemoryRegionType_KernelTemp = KMemoryRegionType_Kernel.Advance(2).Derive(2, 0);
+constexpr inline auto KMemoryRegionType_KernelTemp =
+ KMemoryRegionType_Kernel.Advance(2).Derive(2, 0);
static_assert(KMemoryRegionType_KernelTemp.GetValue() == 0x31);
constexpr KMemoryRegionType GetTypeForVirtualLinearMapping(u32 type_id) {
@@ -335,6 +358,8 @@ constexpr KMemoryRegionType GetTypeForVirtualLinearMapping(u32 type_id) {
return KMemoryRegionType_VirtualDramKernelTraceBuffer;
} else if (KMemoryRegionType_DramKernelPtHeap.IsAncestorOf(type_id)) {
return KMemoryRegionType_VirtualDramKernelPtHeap;
+ } else if (KMemoryRegionType_DramKernelSecureAppletMemory.IsAncestorOf(type_id)) {
+ return KMemoryRegionType_VirtualDramKernelSecureAppletMemory;
} else if ((type_id | KMemoryRegionAttr_ShouldKernelMap) == type_id) {
return KMemoryRegionType_VirtualDramUnknownDebug;
} else {
diff --git a/src/core/hle/kernel/k_page_bitmap.h b/src/core/hle/kernel/k_page_bitmap.h
index c97b3dc0b..0ff987732 100644
--- a/src/core/hle/kernel/k_page_bitmap.h
+++ b/src/core/hle/kernel/k_page_bitmap.h
@@ -16,107 +16,126 @@
namespace Kernel {
class KPageBitmap {
-private:
+public:
class RandomBitGenerator {
- private:
- Common::TinyMT rng{};
- u32 entropy{};
- u32 bits_available{};
+ public:
+ RandomBitGenerator() {
+ m_rng.Initialize(static_cast<u32>(KSystemControl::GenerateRandomU64()));
+ }
+
+ u64 SelectRandomBit(u64 bitmap) {
+ u64 selected = 0;
+
+ for (size_t cur_num_bits = Common::BitSize<decltype(bitmap)>() / 2; cur_num_bits != 0;
+ cur_num_bits /= 2) {
+ const u64 high = (bitmap >> cur_num_bits);
+ const u64 low = (bitmap & (~(UINT64_C(0xFFFFFFFFFFFFFFFF) << cur_num_bits)));
+
+ // Choose high if we have high and (don't have low or select high randomly).
+ if (high && (low == 0 || this->GenerateRandomBit())) {
+ bitmap = high;
+ selected += cur_num_bits;
+ } else {
+ bitmap = low;
+ selected += 0;
+ }
+ }
+
+ return selected;
+ }
+
+ u64 GenerateRandom(u64 max) {
+ // Determine the number of bits we need.
+ const u64 bits_needed = 1 + (Common::BitSize<decltype(max)>() - std::countl_zero(max));
+
+ // Generate a random value of the desired bitwidth.
+ const u64 rnd = this->GenerateRandomBits(static_cast<u32>(bits_needed));
+
+ // Adjust the value to be in range.
+ return rnd - ((rnd / max) * max);
+ }
private:
void RefreshEntropy() {
- entropy = rng.GenerateRandomU32();
- bits_available = static_cast<u32>(Common::BitSize<decltype(entropy)>());
+ m_entropy = m_rng.GenerateRandomU32();
+ m_bits_available = static_cast<u32>(Common::BitSize<decltype(m_entropy)>());
}
bool GenerateRandomBit() {
- if (bits_available == 0) {
+ if (m_bits_available == 0) {
this->RefreshEntropy();
}
- const bool rnd_bit = (entropy & 1) != 0;
- entropy >>= 1;
- --bits_available;
+ const bool rnd_bit = (m_entropy & 1) != 0;
+ m_entropy >>= 1;
+ --m_bits_available;
return rnd_bit;
}
- public:
- RandomBitGenerator() {
- rng.Initialize(static_cast<u32>(KSystemControl::GenerateRandomU64()));
- }
+ u64 GenerateRandomBits(u32 num_bits) {
+ u64 result = 0;
- std::size_t SelectRandomBit(u64 bitmap) {
- u64 selected = 0;
+ // Iteratively add random bits to our result.
+ while (num_bits > 0) {
+ // Ensure we have random bits to take from.
+ if (m_bits_available == 0) {
+ this->RefreshEntropy();
+ }
- u64 cur_num_bits = Common::BitSize<decltype(bitmap)>() / 2;
- u64 cur_mask = (1ULL << cur_num_bits) - 1;
+ // Determine how many bits to take this round.
+ const auto cur_bits = std::min(num_bits, m_bits_available);
- while (cur_num_bits) {
- const u64 low = (bitmap >> 0) & cur_mask;
- const u64 high = (bitmap >> cur_num_bits) & cur_mask;
+ // Generate mask for our current bits.
+ const u64 mask = (static_cast<u64>(1) << cur_bits) - 1;
- bool choose_low;
- if (high == 0) {
- // If only low val is set, choose low.
- choose_low = true;
- } else if (low == 0) {
- // If only high val is set, choose high.
- choose_low = false;
- } else {
- // If both are set, choose random.
- choose_low = this->GenerateRandomBit();
- }
+ // Add bits to output from our entropy.
+ result <<= cur_bits;
+ result |= (m_entropy & mask);
- // If we chose low, proceed with low.
- if (choose_low) {
- bitmap = low;
- selected += 0;
- } else {
- bitmap = high;
- selected += cur_num_bits;
- }
+ // Remove bits from our entropy.
+ m_entropy >>= cur_bits;
+ m_bits_available -= cur_bits;
- // Proceed.
- cur_num_bits /= 2;
- cur_mask >>= cur_num_bits;
+ // Advance.
+ num_bits -= cur_bits;
}
- return selected;
+ return result;
}
+
+ private:
+ Common::TinyMT m_rng;
+ u32 m_entropy{};
+ u32 m_bits_available{};
};
public:
- static constexpr std::size_t MaxDepth = 4;
-
-private:
- std::array<u64*, MaxDepth> bit_storages{};
- RandomBitGenerator rng{};
- std::size_t num_bits{};
- std::size_t used_depths{};
+ static constexpr size_t MaxDepth = 4;
public:
KPageBitmap() = default;
- constexpr std::size_t GetNumBits() const {
- return num_bits;
+ constexpr size_t GetNumBits() const {
+ return m_num_bits;
}
constexpr s32 GetHighestDepthIndex() const {
- return static_cast<s32>(used_depths) - 1;
+ return static_cast<s32>(m_used_depths) - 1;
}
- u64* Initialize(u64* storage, std::size_t size) {
+ u64* Initialize(u64* storage, size_t size) {
// Initially, everything is un-set.
- num_bits = 0;
+ m_num_bits = 0;
// Calculate the needed bitmap depth.
- used_depths = static_cast<std::size_t>(GetRequiredDepth(size));
- ASSERT(used_depths <= MaxDepth);
+ m_used_depths = static_cast<size_t>(GetRequiredDepth(size));
+ ASSERT(m_used_depths <= MaxDepth);
// Set the bitmap pointers.
for (s32 depth = this->GetHighestDepthIndex(); depth >= 0; depth--) {
- bit_storages[depth] = storage;
+ m_bit_storages[depth] = storage;
size = Common::AlignUp(size, Common::BitSize<u64>()) / Common::BitSize<u64>();
storage += size;
+ m_end_storages[depth] = storage;
}
return storage;
@@ -128,19 +147,19 @@ public:
if (random) {
do {
- const u64 v = bit_storages[depth][offset];
+ const u64 v = m_bit_storages[depth][offset];
if (v == 0) {
// If depth is bigger than zero, then a previous level indicated a block was
// free.
ASSERT(depth == 0);
return -1;
}
- offset = offset * Common::BitSize<u64>() + rng.SelectRandomBit(v);
+ offset = offset * Common::BitSize<u64>() + m_rng.SelectRandomBit(v);
++depth;
- } while (depth < static_cast<s32>(used_depths));
+ } while (depth < static_cast<s32>(m_used_depths));
} else {
do {
- const u64 v = bit_storages[depth][offset];
+ const u64 v = m_bit_storages[depth][offset];
if (v == 0) {
// If depth is bigger than zero, then a previous level indicated a block was
// free.
@@ -149,28 +168,69 @@ public:
}
offset = offset * Common::BitSize<u64>() + std::countr_zero(v);
++depth;
- } while (depth < static_cast<s32>(used_depths));
+ } while (depth < static_cast<s32>(m_used_depths));
}
return static_cast<s64>(offset);
}
- void SetBit(std::size_t offset) {
+ s64 FindFreeRange(size_t count) {
+ // Check that it is possible to find a range.
+ const u64* const storage_start = m_bit_storages[m_used_depths - 1];
+ const u64* const storage_end = m_end_storages[m_used_depths - 1];
+
+ // If we don't have a storage to iterate (or want more blocks than fit in a single storage),
+ // we can't find a free range.
+ if (!(storage_start < storage_end && count <= Common::BitSize<u64>())) {
+ return -1;
+ }
+
+ // Walk the storages to select a random free range.
+ const size_t options_per_storage = std::max<size_t>(Common::BitSize<u64>() / count, 1);
+ const size_t num_entries = std::max<size_t>(storage_end - storage_start, 1);
+
+ const u64 free_mask = (static_cast<u64>(1) << count) - 1;
+
+ size_t num_valid_options = 0;
+ s64 chosen_offset = -1;
+ for (size_t storage_index = 0; storage_index < num_entries; ++storage_index) {
+ u64 storage = storage_start[storage_index];
+ for (size_t option = 0; option < options_per_storage; ++option) {
+ if ((storage & free_mask) == free_mask) {
+ // We've found a new valid option.
+ ++num_valid_options;
+
+ // Select the Kth valid option with probability 1/K. This leads to an overall
+ // uniform distribution.
+ if (num_valid_options == 1 || m_rng.GenerateRandom(num_valid_options) == 0) {
+ // This is our first option, so select it.
+ chosen_offset = storage_index * Common::BitSize<u64>() + option * count;
+ }
+ }
+ storage >>= count;
+ }
+ }
+
+ // Return the random offset we chose.*/
+ return chosen_offset;
+ }
+
+ void SetBit(size_t offset) {
this->SetBit(this->GetHighestDepthIndex(), offset);
- num_bits++;
+ m_num_bits++;
}
- void ClearBit(std::size_t offset) {
+ void ClearBit(size_t offset) {
this->ClearBit(this->GetHighestDepthIndex(), offset);
- num_bits--;
+ m_num_bits--;
}
- bool ClearRange(std::size_t offset, std::size_t count) {
+ bool ClearRange(size_t offset, size_t count) {
s32 depth = this->GetHighestDepthIndex();
- u64* bits = bit_storages[depth];
- std::size_t bit_ind = offset / Common::BitSize<u64>();
- if (count < Common::BitSize<u64>()) {
- const std::size_t shift = offset % Common::BitSize<u64>();
+ u64* bits = m_bit_storages[depth];
+ size_t bit_ind = offset / Common::BitSize<u64>();
+ if (count < Common::BitSize<u64>()) [[likely]] {
+ const size_t shift = offset % Common::BitSize<u64>();
ASSERT(shift + count <= Common::BitSize<u64>());
// Check that all the bits are set.
const u64 mask = ((u64(1) << count) - 1) << shift;
@@ -189,8 +249,8 @@ public:
ASSERT(offset % Common::BitSize<u64>() == 0);
ASSERT(count % Common::BitSize<u64>() == 0);
// Check that all the bits are set.
- std::size_t remaining = count;
- std::size_t i = 0;
+ size_t remaining = count;
+ size_t i = 0;
do {
if (bits[bit_ind + i++] != ~u64(0)) {
return false;
@@ -209,18 +269,18 @@ public:
} while (remaining > 0);
}
- num_bits -= count;
+ m_num_bits -= count;
return true;
}
private:
- void SetBit(s32 depth, std::size_t offset) {
+ void SetBit(s32 depth, size_t offset) {
while (depth >= 0) {
- std::size_t ind = offset / Common::BitSize<u64>();
- std::size_t which = offset % Common::BitSize<u64>();
+ size_t ind = offset / Common::BitSize<u64>();
+ size_t which = offset % Common::BitSize<u64>();
const u64 mask = u64(1) << which;
- u64* bit = std::addressof(bit_storages[depth][ind]);
+ u64* bit = std::addressof(m_bit_storages[depth][ind]);
u64 v = *bit;
ASSERT((v & mask) == 0);
*bit = v | mask;
@@ -232,13 +292,13 @@ private:
}
}
- void ClearBit(s32 depth, std::size_t offset) {
+ void ClearBit(s32 depth, size_t offset) {
while (depth >= 0) {
- std::size_t ind = offset / Common::BitSize<u64>();
- std::size_t which = offset % Common::BitSize<u64>();
+ size_t ind = offset / Common::BitSize<u64>();
+ size_t which = offset % Common::BitSize<u64>();
const u64 mask = u64(1) << which;
- u64* bit = std::addressof(bit_storages[depth][ind]);
+ u64* bit = std::addressof(m_bit_storages[depth][ind]);
u64 v = *bit;
ASSERT((v & mask) != 0);
v &= ~mask;
@@ -252,7 +312,7 @@ private:
}
private:
- static constexpr s32 GetRequiredDepth(std::size_t region_size) {
+ static constexpr s32 GetRequiredDepth(size_t region_size) {
s32 depth = 0;
while (true) {
region_size /= Common::BitSize<u64>();
@@ -264,8 +324,8 @@ private:
}
public:
- static constexpr std::size_t CalculateManagementOverheadSize(std::size_t region_size) {
- std::size_t overhead_bits = 0;
+ static constexpr size_t CalculateManagementOverheadSize(size_t region_size) {
+ size_t overhead_bits = 0;
for (s32 depth = GetRequiredDepth(region_size) - 1; depth >= 0; depth--) {
region_size =
Common::AlignUp(region_size, Common::BitSize<u64>()) / Common::BitSize<u64>();
@@ -273,6 +333,13 @@ public:
}
return overhead_bits * sizeof(u64);
}
+
+private:
+ std::array<u64*, MaxDepth> m_bit_storages{};
+ std::array<u64*, MaxDepth> m_end_storages{};
+ RandomBitGenerator m_rng;
+ size_t m_num_bits{};
+ size_t m_used_depths{};
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_buffer.h b/src/core/hle/kernel/k_page_buffer.h
index aef06e213..cfedaae61 100644
--- a/src/core/hle/kernel/k_page_buffer.h
+++ b/src/core/hle/kernel/k_page_buffer.h
@@ -11,6 +11,16 @@
namespace Kernel {
+class KernelCore;
+
+class KPageBufferSlabHeap : protected impl::KSlabHeapImpl {
+public:
+ static constexpr size_t BufferSize = PageSize;
+
+public:
+ void Initialize(Core::System& system);
+};
+
class KPageBuffer final : public KSlabAllocated<KPageBuffer> {
public:
explicit KPageBuffer(KernelCore&) {}
@@ -21,8 +31,6 @@ public:
private:
[[maybe_unused]] alignas(PageSize) std::array<u8, PageSize> m_buffer{};
};
-
-static_assert(sizeof(KPageBuffer) == PageSize);
-static_assert(alignof(KPageBuffer) == PageSize);
+static_assert(sizeof(KPageBuffer) == KPageBufferSlabHeap::BufferSize);
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_group.h b/src/core/hle/kernel/k_page_group.h
index 968753992..316f172f2 100644
--- a/src/core/hle/kernel/k_page_group.h
+++ b/src/core/hle/kernel/k_page_group.h
@@ -5,6 +5,7 @@
#include <list>
+#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "core/hle/kernel/memory_types.h"
@@ -12,6 +13,89 @@
namespace Kernel {
+class KPageGroup;
+
+class KBlockInfo {
+private:
+ friend class KPageGroup;
+
+public:
+ constexpr KBlockInfo() = default;
+
+ constexpr void Initialize(PAddr addr, size_t np) {
+ ASSERT(Common::IsAligned(addr, PageSize));
+ ASSERT(static_cast<u32>(np) == np);
+
+ m_page_index = static_cast<u32>(addr) / PageSize;
+ m_num_pages = static_cast<u32>(np);
+ }
+
+ constexpr PAddr GetAddress() const {
+ return m_page_index * PageSize;
+ }
+ constexpr size_t GetNumPages() const {
+ return m_num_pages;
+ }
+ constexpr size_t GetSize() const {
+ return this->GetNumPages() * PageSize;
+ }
+ constexpr PAddr GetEndAddress() const {
+ return (m_page_index + m_num_pages) * PageSize;
+ }
+ constexpr PAddr GetLastAddress() const {
+ return this->GetEndAddress() - 1;
+ }
+
+ constexpr KBlockInfo* GetNext() const {
+ return m_next;
+ }
+
+ constexpr bool IsEquivalentTo(const KBlockInfo& rhs) const {
+ return m_page_index == rhs.m_page_index && m_num_pages == rhs.m_num_pages;
+ }
+
+ constexpr bool operator==(const KBlockInfo& rhs) const {
+ return this->IsEquivalentTo(rhs);
+ }
+
+ constexpr bool operator!=(const KBlockInfo& rhs) const {
+ return !(*this == rhs);
+ }
+
+ constexpr bool IsStrictlyBefore(PAddr addr) const {
+ const PAddr end = this->GetEndAddress();
+
+ if (m_page_index != 0 && end == 0) {
+ return false;
+ }
+
+ return end < addr;
+ }
+
+ constexpr bool operator<(PAddr addr) const {
+ return this->IsStrictlyBefore(addr);
+ }
+
+ constexpr bool TryConcatenate(PAddr addr, size_t np) {
+ if (addr != 0 && addr == this->GetEndAddress()) {
+ m_num_pages += static_cast<u32>(np);
+ return true;
+ }
+ return false;
+ }
+
+private:
+ constexpr void SetNext(KBlockInfo* next) {
+ m_next = next;
+ }
+
+private:
+ KBlockInfo* m_next{};
+ u32 m_page_index{};
+ u32 m_num_pages{};
+};
+static_assert(sizeof(KBlockInfo) <= 0x10);
+
class KPageGroup final {
public:
class Node final {
@@ -92,6 +176,8 @@ public:
return nodes.empty();
}
+ void Finalize() {}
+
private:
std::list<Node> nodes;
};
diff --git a/src/core/hle/kernel/k_page_heap.cpp b/src/core/hle/kernel/k_page_heap.cpp
index 5ede60168..7b02c7d8b 100644
--- a/src/core/hle/kernel/k_page_heap.cpp
+++ b/src/core/hle/kernel/k_page_heap.cpp
@@ -44,11 +44,11 @@ size_t KPageHeap::GetNumFreePages() const {
return num_free;
}
-PAddr KPageHeap::AllocateBlock(s32 index, bool random) {
+PAddr KPageHeap::AllocateByLinearSearch(s32 index) {
const size_t needed_size = m_blocks[index].GetSize();
for (s32 i = index; i < static_cast<s32>(m_num_blocks); i++) {
- if (const PAddr addr = m_blocks[i].PopBlock(random); addr != 0) {
+ if (const PAddr addr = m_blocks[i].PopBlock(false); addr != 0) {
if (const size_t allocated_size = m_blocks[i].GetSize(); allocated_size > needed_size) {
this->Free(addr + needed_size, (allocated_size - needed_size) / PageSize);
}
@@ -59,6 +59,88 @@ PAddr KPageHeap::AllocateBlock(s32 index, bool random) {
return 0;
}
+PAddr KPageHeap::AllocateByRandom(s32 index, size_t num_pages, size_t align_pages) {
+ // Get the size and required alignment.
+ const size_t needed_size = num_pages * PageSize;
+ const size_t align_size = align_pages * PageSize;
+
+ // Determine meta-alignment of our desired alignment size.
+ const size_t align_shift = std::countr_zero(align_size);
+
+ // Decide on a block to allocate from.
+ constexpr size_t MinimumPossibleAlignmentsForRandomAllocation = 4;
+ {
+ // By default, we'll want to look at all blocks larger than our current one.
+ s32 max_blocks = static_cast<s32>(m_num_blocks);
+
+ // Determine the maximum block we should try to allocate from.
+ size_t possible_alignments = 0;
+ for (s32 i = index; i < max_blocks; ++i) {
+ // Add the possible alignments from blocks at the current size.
+ possible_alignments += (1 + ((m_blocks[i].GetSize() - needed_size) >> align_shift)) *
+ m_blocks[i].GetNumFreeBlocks();
+
+ // If there are enough possible alignments, we don't need to look at larger blocks.
+ if (possible_alignments >= MinimumPossibleAlignmentsForRandomAllocation) {
+ max_blocks = i + 1;
+ break;
+ }
+ }
+
+ // If we have any possible alignments which require a larger block, we need to pick one.
+ if (possible_alignments > 0 && index + 1 < max_blocks) {
+ // Select a random alignment from the possibilities.
+ const size_t rnd = m_rng.GenerateRandom(possible_alignments);
+
+ // Determine which block corresponds to the random alignment we chose.
+ possible_alignments = 0;
+ for (s32 i = index; i < max_blocks; ++i) {
+ // Add the possible alignments from blocks at the current size.
+ possible_alignments +=
+ (1 + ((m_blocks[i].GetSize() - needed_size) >> align_shift)) *
+ m_blocks[i].GetNumFreeBlocks();
+
+ // If the current block gets us to our random choice, use the current block.
+ if (rnd < possible_alignments) {
+ index = i;
+ break;
+ }
+ }
+ }
+ }
+
+ // Pop a block from the index we selected.
+ if (PAddr addr = m_blocks[index].PopBlock(true); addr != 0) {
+ // Determine how much size we have left over.
+ if (const size_t leftover_size = m_blocks[index].GetSize() - needed_size;
+ leftover_size > 0) {
+ // Determine how many valid alignments we can have.
+ const size_t possible_alignments = 1 + (leftover_size >> align_shift);
+
+ // Select a random valid alignment.
+ const size_t random_offset = m_rng.GenerateRandom(possible_alignments) << align_shift;
+
+ // Free memory before the random offset.
+ if (random_offset != 0) {
+ this->Free(addr, random_offset / PageSize);
+ }
+
+ // Advance our block by the random offset.
+ addr += random_offset;
+
+ // Free memory after our allocated block.
+ if (random_offset != leftover_size) {
+ this->Free(addr + needed_size, (leftover_size - random_offset) / PageSize);
+ }
+ }
+
+ // Return the block we allocated.
+ return addr;
+ }
+
+ return 0;
+}
+
void KPageHeap::FreeBlock(PAddr block, s32 index) {
do {
block = m_blocks[index++].PushBlock(block);
diff --git a/src/core/hle/kernel/k_page_heap.h b/src/core/hle/kernel/k_page_heap.h
index 0917a8bed..9021edcf7 100644
--- a/src/core/hle/kernel/k_page_heap.h
+++ b/src/core/hle/kernel/k_page_heap.h
@@ -14,13 +14,9 @@
namespace Kernel {
-class KPageHeap final {
+class KPageHeap {
public:
- YUZU_NON_COPYABLE(KPageHeap);
- YUZU_NON_MOVEABLE(KPageHeap);
-
KPageHeap() = default;
- ~KPageHeap() = default;
constexpr PAddr GetAddress() const {
return m_heap_address;
@@ -57,7 +53,20 @@ public:
m_initial_used_size = m_heap_size - free_size - reserved_size;
}
- PAddr AllocateBlock(s32 index, bool random);
+ PAddr AllocateBlock(s32 index, bool random) {
+ if (random) {
+ const size_t block_pages = m_blocks[index].GetNumPages();
+ return this->AllocateByRandom(index, block_pages, block_pages);
+ } else {
+ return this->AllocateByLinearSearch(index);
+ }
+ }
+
+ PAddr AllocateAligned(s32 index, size_t num_pages, size_t align_pages) {
+ // TODO: linear search support?
+ return this->AllocateByRandom(index, num_pages, align_pages);
+ }
+
void Free(PAddr addr, size_t num_pages);
static size_t CalculateManagementOverheadSize(size_t region_size) {
@@ -68,7 +77,7 @@ public:
static constexpr s32 GetAlignedBlockIndex(size_t num_pages, size_t align_pages) {
const size_t target_pages = std::max(num_pages, align_pages);
for (size_t i = 0; i < NumMemoryBlockPageShifts; i++) {
- if (target_pages <= (size_t(1) << MemoryBlockPageShifts[i]) / PageSize) {
+ if (target_pages <= (static_cast<size_t>(1) << MemoryBlockPageShifts[i]) / PageSize) {
return static_cast<s32>(i);
}
}
@@ -77,7 +86,7 @@ public:
static constexpr s32 GetBlockIndex(size_t num_pages) {
for (s32 i = static_cast<s32>(NumMemoryBlockPageShifts) - 1; i >= 0; i--) {
- if (num_pages >= (size_t(1) << MemoryBlockPageShifts[i]) / PageSize) {
+ if (num_pages >= (static_cast<size_t>(1) << MemoryBlockPageShifts[i]) / PageSize) {
return i;
}
}
@@ -85,7 +94,7 @@ public:
}
static constexpr size_t GetBlockSize(size_t index) {
- return size_t(1) << MemoryBlockPageShifts[index];
+ return static_cast<size_t>(1) << MemoryBlockPageShifts[index];
}
static constexpr size_t GetBlockNumPages(size_t index) {
@@ -93,13 +102,9 @@ public:
}
private:
- class Block final {
+ class Block {
public:
- YUZU_NON_COPYABLE(Block);
- YUZU_NON_MOVEABLE(Block);
-
Block() = default;
- ~Block() = default;
constexpr size_t GetShift() const {
return m_block_shift;
@@ -201,6 +206,9 @@ private:
};
private:
+ PAddr AllocateByLinearSearch(s32 index);
+ PAddr AllocateByRandom(s32 index, size_t num_pages, size_t align_pages);
+
static size_t CalculateManagementOverheadSize(size_t region_size, const size_t* block_shifts,
size_t num_block_shifts);
@@ -209,7 +217,8 @@ private:
size_t m_heap_size{};
size_t m_initial_used_size{};
size_t m_num_blocks{};
- std::array<Block, NumMemoryBlockPageShifts> m_blocks{};
+ std::array<Block, NumMemoryBlockPageShifts> m_blocks;
+ KPageBitmap::RandomBitGenerator m_rng;
std::vector<u64> m_management_data;
};
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 307e491cb..5387bf5fe 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -15,6 +15,7 @@
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_system_control.h"
+#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
#include "core/memory.h"
@@ -23,6 +24,61 @@ namespace Kernel {
namespace {
+class KScopedLightLockPair {
+ YUZU_NON_COPYABLE(KScopedLightLockPair);
+ YUZU_NON_MOVEABLE(KScopedLightLockPair);
+
+private:
+ KLightLock* m_lower;
+ KLightLock* m_upper;
+
+public:
+ KScopedLightLockPair(KLightLock& lhs, KLightLock& rhs) {
+ // Ensure our locks are in a consistent order.
+ if (std::addressof(lhs) <= std::addressof(rhs)) {
+ m_lower = std::addressof(lhs);
+ m_upper = std::addressof(rhs);
+ } else {
+ m_lower = std::addressof(rhs);
+ m_upper = std::addressof(lhs);
+ }
+
+ // Acquire both locks.
+ m_lower->Lock();
+ if (m_lower != m_upper) {
+ m_upper->Lock();
+ }
+ }
+
+ ~KScopedLightLockPair() {
+ // Unlock the upper lock.
+ if (m_upper != nullptr && m_upper != m_lower) {
+ m_upper->Unlock();
+ }
+
+ // Unlock the lower lock.
+ if (m_lower != nullptr) {
+ m_lower->Unlock();
+ }
+ }
+
+public:
+ // Utility.
+ void TryUnlockHalf(KLightLock& lock) {
+ // Only allow unlocking if the lock is half the pair.
+ if (m_lower != m_upper) {
+ // We want to be sure the lock is one we own.
+ if (m_lower == std::addressof(lock)) {
+ lock.Unlock();
+ m_lower = nullptr;
+ } else if (m_upper == std::addressof(lock)) {
+ lock.Unlock();
+ m_upper = nullptr;
+ }
+ }
+ }
+};
+
using namespace Common::Literals;
constexpr size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType as_type) {
@@ -49,9 +105,10 @@ KPageTable::KPageTable(Core::System& system_)
KPageTable::~KPageTable() = default;
Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
- VAddr code_addr, size_t code_size,
- KMemoryBlockSlabManager* mem_block_slab_manager,
- KMemoryManager::Pool pool) {
+ bool enable_das_merge, bool from_back,
+ KMemoryManager::Pool pool, VAddr code_addr,
+ size_t code_size, KSystemResource* system_resource,
+ KResourceLimit* resource_limit) {
const auto GetSpaceStart = [this](KAddressSpaceInfo::Type type) {
return KAddressSpaceInfo::GetAddressSpaceStart(m_address_space_width, type);
@@ -112,11 +169,13 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type
// Set other basic fields
m_enable_aslr = enable_aslr;
- m_enable_device_address_space_merge = false;
+ m_enable_device_address_space_merge = enable_das_merge;
m_address_space_start = start;
m_address_space_end = end;
m_is_kernel = false;
- m_memory_block_slab_manager = mem_block_slab_manager;
+ m_memory_block_slab_manager = system_resource->GetMemoryBlockSlabManagerPointer();
+ m_block_info_manager = system_resource->GetBlockInfoManagerPointer();
+ m_resource_limit = resource_limit;
// Determine the region we can place our undetermineds in
VAddr alloc_start{};
@@ -215,10 +274,22 @@ Result KPageTable::InitializeForProcess(FileSys::ProgramAddressSpaceType as_type
}
}
- // Set heap members
+ // Set heap and fill members.
m_current_heap_end = m_heap_region_start;
m_max_heap_size = 0;
- m_max_physical_memory_size = 0;
+ m_mapped_physical_memory_size = 0;
+ m_mapped_unsafe_physical_memory = 0;
+ m_mapped_insecure_memory = 0;
+ m_mapped_ipc_server_memory = 0;
+
+ m_heap_fill_value = 0;
+ m_ipc_fill_value = 0;
+ m_stack_fill_value = 0;
+
+ // Set allocation option.
+ m_allocate_option =
+ KMemoryManager::EncodeOption(pool, from_back ? KMemoryManager::Direction::FromBack
+ : KMemoryManager::Direction::FromFront);
// Ensure that we regions inside our address space
auto IsInAddressSpace = [&](VAddr addr) {
@@ -267,6 +338,16 @@ void KPageTable::Finalize() {
m_system.Memory().UnmapRegion(*m_page_table_impl, addr, size);
});
+ // Release any insecure mapped memory.
+ if (m_mapped_insecure_memory) {
+ UNIMPLEMENTED();
+ }
+
+ // Release any ipc server memory.
+ if (m_mapped_ipc_server_memory) {
+ UNIMPLEMENTED();
+ }
+
// Close the backing page table, as the destructor is not called for guest objects.
m_page_table_impl.reset();
}
@@ -650,7 +731,8 @@ bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t nu
Result KPageTable::UnmapProcessMemory(VAddr dst_addr, size_t size, KPageTable& src_page_table,
VAddr src_addr) {
- KScopedLightLock lk(m_general_lock);
+ // Acquire the table locks.
+ KScopedLightLockPair lk(src_page_table.m_general_lock, m_general_lock);
const size_t num_pages{size / PageSize};
@@ -686,9 +768,753 @@ Result KPageTable::UnmapProcessMemory(VAddr dst_addr, size_t size, KPageTable& s
R_SUCCEED();
}
+Result KPageTable::SetupForIpcClient(PageLinkedList* page_list, size_t* out_blocks_needed,
+ VAddr address, size_t size, KMemoryPermission test_perm,
+ KMemoryState dst_state) {
+ // Validate pre-conditions.
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(test_perm == KMemoryPermission::UserReadWrite ||
+ test_perm == KMemoryPermission::UserRead);
+
+ // Check that the address is in range.
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Get the source permission.
+ const auto src_perm = (test_perm == KMemoryPermission::UserReadWrite)
+ ? KMemoryPermission::KernelReadWrite | KMemoryPermission::NotMapped
+ : KMemoryPermission::UserRead;
+
+ // Get aligned extents.
+ const VAddr aligned_src_start = Common::AlignDown((address), PageSize);
+ const VAddr aligned_src_end = Common::AlignUp((address) + size, PageSize);
+ const VAddr mapping_src_start = Common::AlignUp((address), PageSize);
+ const VAddr mapping_src_end = Common::AlignDown((address) + size, PageSize);
+
+ const auto aligned_src_last = (aligned_src_end)-1;
+ const auto mapping_src_last = (mapping_src_end)-1;
+
+ // Get the test state and attribute mask.
+ KMemoryState test_state;
+ KMemoryAttribute test_attr_mask;
+ switch (dst_state) {
+ case KMemoryState::Ipc:
+ test_state = KMemoryState::FlagCanUseIpc;
+ test_attr_mask =
+ KMemoryAttribute::Uncached | KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonSecureIpc:
+ test_state = KMemoryState::FlagCanUseNonSecureIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonDeviceIpc:
+ test_state = KMemoryState::FlagCanUseNonDeviceIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ default:
+ R_THROW(ResultInvalidCombination);
+ }
+
+ // Ensure that on failure, we roll back appropriately.
+ size_t mapped_size = 0;
+ ON_RESULT_FAILURE {
+ if (mapped_size > 0) {
+ this->CleanupForIpcClientOnServerSetupFailure(page_list, mapping_src_start, mapped_size,
+ src_perm);
+ }
+ };
+
+ size_t blocks_needed = 0;
+
+ // Iterate, mapping as needed.
+ KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(aligned_src_start);
+ while (true) {
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ // Validate the current block.
+ R_TRY(this->CheckMemoryState(info, test_state, test_state, test_perm, test_perm,
+ test_attr_mask, KMemoryAttribute::None));
+
+ if (mapping_src_start < mapping_src_end && (mapping_src_start) < info.GetEndAddress() &&
+ info.GetAddress() < (mapping_src_end)) {
+ const auto cur_start =
+ info.GetAddress() >= (mapping_src_start) ? info.GetAddress() : (mapping_src_start);
+ const auto cur_end = mapping_src_last >= info.GetLastAddress() ? info.GetEndAddress()
+ : (mapping_src_end);
+ const size_t cur_size = cur_end - cur_start;
+
+ if (info.GetAddress() < (mapping_src_start)) {
+ ++blocks_needed;
+ }
+ if (mapping_src_last < info.GetLastAddress()) {
+ ++blocks_needed;
+ }
+
+ // Set the permissions on the block, if we need to.
+ if ((info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != src_perm) {
+ R_TRY(Operate(cur_start, cur_size / PageSize, src_perm,
+ OperationType::ChangePermissions));
+ }
+
+ // Note that we mapped this part.
+ mapped_size += cur_size;
+ }
+
+ // If the block is at the end, we're done.
+ if (aligned_src_last <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ ++it;
+ ASSERT(it != m_memory_block_manager.end());
+ }
+
+ if (out_blocks_needed != nullptr) {
+ ASSERT(blocks_needed <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
+ *out_blocks_needed = blocks_needed;
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_addr,
+ KMemoryPermission test_perm, KMemoryState dst_state,
+ KPageTable& src_page_table, bool send) {
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(src_page_table.IsLockedByCurrentThread());
+
+ // Check that we can theoretically map.
+ const VAddr region_start = m_alias_region_start;
+ const size_t region_size = m_alias_region_end - m_alias_region_start;
+ R_UNLESS(size < region_size, ResultOutOfAddressSpace);
+
+ // Get aligned source extents.
+ const VAddr src_start = src_addr;
+ const VAddr src_end = src_addr + size;
+ const VAddr aligned_src_start = Common::AlignDown((src_start), PageSize);
+ const VAddr aligned_src_end = Common::AlignUp((src_start) + size, PageSize);
+ const VAddr mapping_src_start = Common::AlignUp((src_start), PageSize);
+ const VAddr mapping_src_end = Common::AlignDown((src_start) + size, PageSize);
+ const size_t aligned_src_size = aligned_src_end - aligned_src_start;
+ const size_t mapping_src_size =
+ (mapping_src_start < mapping_src_end) ? (mapping_src_end - mapping_src_start) : 0;
+
+ // Select a random address to map at.
+ VAddr dst_addr =
+ this->FindFreeArea(region_start, region_size / PageSize, aligned_src_size / PageSize,
+ PageSize, 0, this->GetNumGuardPages());
+
+ R_UNLESS(dst_addr != 0, ResultOutOfAddressSpace);
+
+ // Check that we can perform the operation we're about to perform.
+ ASSERT(this->CanContain(dst_addr, aligned_src_size, dst_state));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Reserve space for any partial pages we allocate.
+ const size_t unmapped_size = aligned_src_size - mapping_src_size;
+ KScopedResourceReservation memory_reservation(
+ m_resource_limit, LimitableResource::PhysicalMemoryMax, unmapped_size);
+ R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
+
+ // Ensure that we manage page references correctly.
+ PAddr start_partial_page = 0;
+ PAddr end_partial_page = 0;
+ VAddr cur_mapped_addr = dst_addr;
+
+ // If the partial pages are mapped, an extra reference will have been opened. Otherwise, they'll
+ // free on scope exit.
+ SCOPE_EXIT({
+ if (start_partial_page != 0) {
+ m_system.Kernel().MemoryManager().Close(start_partial_page, 1);
+ }
+ if (end_partial_page != 0) {
+ m_system.Kernel().MemoryManager().Close(end_partial_page, 1);
+ }
+ });
+
+ ON_RESULT_FAILURE {
+ if (cur_mapped_addr != dst_addr) {
+ // HACK: Manually close the pages.
+ HACK_ClosePages(dst_addr, (cur_mapped_addr - dst_addr) / PageSize);
+
+ ASSERT(Operate(dst_addr, (cur_mapped_addr - dst_addr) / PageSize,
+ KMemoryPermission::None, OperationType::Unmap)
+ .IsSuccess());
+ }
+ };
+
+ // Allocate the start page as needed.
+ if (aligned_src_start < mapping_src_start) {
+ start_partial_page =
+ m_system.Kernel().MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option);
+ R_UNLESS(start_partial_page != 0, ResultOutOfMemory);
+ }
+
+ // Allocate the end page as needed.
+ if (mapping_src_end < aligned_src_end &&
+ (aligned_src_start < mapping_src_end || aligned_src_start == mapping_src_start)) {
+ end_partial_page =
+ m_system.Kernel().MemoryManager().AllocateAndOpenContinuous(1, 1, m_allocate_option);
+ R_UNLESS(end_partial_page != 0, ResultOutOfMemory);
+ }
+
+ // Get the implementation.
+ auto& src_impl = src_page_table.PageTableImpl();
+
+ // Get the fill value for partial pages.
+ const auto fill_val = m_ipc_fill_value;
+
+ // Begin traversal.
+ Common::PageTable::TraversalContext context;
+ Common::PageTable::TraversalEntry next_entry;
+ bool traverse_valid = src_impl.BeginTraversal(next_entry, context, aligned_src_start);
+ ASSERT(traverse_valid);
+
+ // Prepare tracking variables.
+ PAddr cur_block_addr = next_entry.phys_addr;
+ size_t cur_block_size =
+ next_entry.block_size - ((cur_block_addr) & (next_entry.block_size - 1));
+ size_t tot_block_size = cur_block_size;
+
+ // Map the start page, if we have one.
+ if (start_partial_page != 0) {
+ // Ensure the page holds correct data.
+ const VAddr start_partial_virt =
+ GetHeapVirtualAddress(m_system.Kernel().MemoryLayout(), start_partial_page);
+ if (send) {
+ const size_t partial_offset = src_start - aligned_src_start;
+ size_t copy_size, clear_size;
+ if (src_end < mapping_src_start) {
+ copy_size = size;
+ clear_size = mapping_src_start - src_end;
+ } else {
+ copy_size = mapping_src_start - src_start;
+ clear_size = 0;
+ }
+
+ std::memset(m_system.Memory().GetPointer<void>(start_partial_virt), fill_val,
+ partial_offset);
+ std::memcpy(
+ m_system.Memory().GetPointer<void>(start_partial_virt + partial_offset),
+ m_system.Memory().GetPointer<void>(
+ GetHeapVirtualAddress(m_system.Kernel().MemoryLayout(), cur_block_addr) +
+ partial_offset),
+ copy_size);
+ if (clear_size > 0) {
+ std::memset(m_system.Memory().GetPointer<void>(start_partial_virt + partial_offset +
+ copy_size),
+ fill_val, clear_size);
+ }
+ } else {
+ std::memset(m_system.Memory().GetPointer<void>(start_partial_virt), fill_val, PageSize);
+ }
+
+ // Map the page.
+ R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, start_partial_page));
+
+ // HACK: Manually open the pages.
+ HACK_OpenPages(start_partial_page, 1);
+
+ // Update tracking extents.
+ cur_mapped_addr += PageSize;
+ cur_block_addr += PageSize;
+ cur_block_size -= PageSize;
+
+ // If the block's size was one page, we may need to continue traversal.
+ if (cur_block_size == 0 && aligned_src_size > PageSize) {
+ traverse_valid = src_impl.ContinueTraversal(next_entry, context);
+ ASSERT(traverse_valid);
+
+ cur_block_addr = next_entry.phys_addr;
+ cur_block_size = next_entry.block_size;
+ tot_block_size += next_entry.block_size;
+ }
+ }
+
+ // Map the remaining pages.
+ while (aligned_src_start + tot_block_size < mapping_src_end) {
+ // Continue the traversal.
+ traverse_valid = src_impl.ContinueTraversal(next_entry, context);
+ ASSERT(traverse_valid);
+
+ // Process the block.
+ if (next_entry.phys_addr != cur_block_addr + cur_block_size) {
+ // Map the block we've been processing so far.
+ R_TRY(Operate(cur_mapped_addr, cur_block_size / PageSize, test_perm, OperationType::Map,
+ cur_block_addr));
+
+ // HACK: Manually open the pages.
+ HACK_OpenPages(cur_block_addr, cur_block_size / PageSize);
+
+ // Update tracking extents.
+ cur_mapped_addr += cur_block_size;
+ cur_block_addr = next_entry.phys_addr;
+ cur_block_size = next_entry.block_size;
+ } else {
+ cur_block_size += next_entry.block_size;
+ }
+ tot_block_size += next_entry.block_size;
+ }
+
+ // Handle the last direct-mapped page.
+ if (const VAddr mapped_block_end = aligned_src_start + tot_block_size - cur_block_size;
+ mapped_block_end < mapping_src_end) {
+ const size_t last_block_size = mapping_src_end - mapped_block_end;
+
+ // Map the last block.
+ R_TRY(Operate(cur_mapped_addr, last_block_size / PageSize, test_perm, OperationType::Map,
+ cur_block_addr));
+
+ // HACK: Manually open the pages.
+ HACK_OpenPages(cur_block_addr, last_block_size / PageSize);
+
+ // Update tracking extents.
+ cur_mapped_addr += last_block_size;
+ cur_block_addr += last_block_size;
+ if (mapped_block_end + cur_block_size < aligned_src_end &&
+ cur_block_size == last_block_size) {
+ traverse_valid = src_impl.ContinueTraversal(next_entry, context);
+ ASSERT(traverse_valid);
+
+ cur_block_addr = next_entry.phys_addr;
+ }
+ }
+
+ // Map the end page, if we have one.
+ if (end_partial_page != 0) {
+ // Ensure the page holds correct data.
+ const VAddr end_partial_virt =
+ GetHeapVirtualAddress(m_system.Kernel().MemoryLayout(), end_partial_page);
+ if (send) {
+ const size_t copy_size = src_end - mapping_src_end;
+ std::memcpy(m_system.Memory().GetPointer<void>(end_partial_virt),
+ m_system.Memory().GetPointer<void>(GetHeapVirtualAddress(
+ m_system.Kernel().MemoryLayout(), cur_block_addr)),
+ copy_size);
+ std::memset(m_system.Memory().GetPointer<void>(end_partial_virt + copy_size), fill_val,
+ PageSize - copy_size);
+ } else {
+ std::memset(m_system.Memory().GetPointer<void>(end_partial_virt), fill_val, PageSize);
+ }
+
+ // Map the page.
+ R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, end_partial_page));
+
+ // HACK: Manually open the pages.
+ HACK_OpenPages(end_partial_page, 1);
+ }
+
+ // Update memory blocks to reflect our changes
+ m_memory_block_manager.Update(std::addressof(allocator), dst_addr, aligned_src_size / PageSize,
+ dst_state, test_perm, KMemoryAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal,
+ KMemoryBlockDisableMergeAttribute::None);
+
+ // Set the output address.
+ *out_addr = dst_addr + (src_start - aligned_src_start);
+
+ // We succeeded.
+ memory_reservation.Commit();
+ R_SUCCEED();
+}
+
+Result KPageTable::SetupForIpc(VAddr* out_dst_addr, size_t size, VAddr src_addr,
+ KPageTable& src_page_table, KMemoryPermission test_perm,
+ KMemoryState dst_state, bool send) {
+ // For convenience, alias this.
+ KPageTable& dst_page_table = *this;
+
+ // Acquire the table locks.
+ KScopedLightLockPair lk(src_page_table.m_general_lock, dst_page_table.m_general_lock);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(std::addressof(src_page_table));
+
+ // Perform client setup.
+ size_t num_allocator_blocks;
+ R_TRY(src_page_table.SetupForIpcClient(updater.GetPageList(),
+ std::addressof(num_allocator_blocks), src_addr, size,
+ test_perm, dst_state));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ src_page_table.m_memory_block_slab_manager,
+ num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // Get the mapped extents.
+ const VAddr src_map_start = Common::AlignUp((src_addr), PageSize);
+ const VAddr src_map_end = Common::AlignDown((src_addr) + size, PageSize);
+ const size_t src_map_size = src_map_end - src_map_start;
+
+ // Ensure that we clean up appropriately if we fail after this.
+ const auto src_perm = (test_perm == KMemoryPermission::UserReadWrite)
+ ? KMemoryPermission::KernelReadWrite | KMemoryPermission::NotMapped
+ : KMemoryPermission::UserRead;
+ ON_RESULT_FAILURE {
+ if (src_map_end > src_map_start) {
+ src_page_table.CleanupForIpcClientOnServerSetupFailure(
+ updater.GetPageList(), src_map_start, src_map_size, src_perm);
+ }
+ };
+
+ // Perform server setup.
+ R_TRY(dst_page_table.SetupForIpcServer(out_dst_addr, size, src_addr, test_perm, dst_state,
+ src_page_table, send));
+
+ // If anything was mapped, ipc-lock the pages.
+ if (src_map_start < src_map_end) {
+ // Get the source permission.
+ src_page_table.m_memory_block_manager.UpdateLock(std::addressof(allocator), src_map_start,
+ (src_map_end - src_map_start) / PageSize,
+ &KMemoryBlock::LockForIpc, src_perm);
+ }
+
+ R_SUCCEED();
+}
+
+Result KPageTable::CleanupForIpcServer(VAddr address, size_t size, KMemoryState dst_state) {
+ // Validate the address.
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Lock the table.
+ KScopedLightLock lk(m_general_lock);
+
+ // Validate the memory state.
+ size_t num_allocator_blocks;
+ R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size,
+ KMemoryState::All, dst_state, KMemoryPermission::UserRead,
+ KMemoryPermission::UserRead, KMemoryAttribute::All,
+ KMemoryAttribute::None));
+
+ // Create an update allocator.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, num_allocator_blocks);
+ R_TRY(allocator_result);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Get aligned extents.
+ const VAddr aligned_start = Common::AlignDown((address), PageSize);
+ const VAddr aligned_end = Common::AlignUp((address) + size, PageSize);
+ const size_t aligned_size = aligned_end - aligned_start;
+ const size_t aligned_num_pages = aligned_size / PageSize;
+
+ // HACK: Manually close the pages.
+ HACK_ClosePages(aligned_start, aligned_num_pages);
+
+ // Unmap the pages.
+ R_TRY(Operate(aligned_start, aligned_num_pages, KMemoryPermission::None, OperationType::Unmap));
+
+ // Update memory blocks.
+ m_memory_block_manager.Update(std::addressof(allocator), aligned_start, aligned_num_pages,
+ KMemoryState::None, KMemoryPermission::None,
+ KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
+ KMemoryBlockDisableMergeAttribute::Normal);
+
+ // Release from the resource limit as relevant.
+ const VAddr mapping_start = Common::AlignUp((address), PageSize);
+ const VAddr mapping_end = Common::AlignDown((address) + size, PageSize);
+ const size_t mapping_size = (mapping_start < mapping_end) ? mapping_end - mapping_start : 0;
+ m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, aligned_size - mapping_size);
+
+ R_SUCCEED();
+}
+
+Result KPageTable::CleanupForIpcClient(VAddr address, size_t size, KMemoryState dst_state) {
+ // Validate the address.
+ R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
+
+ // Get aligned source extents.
+ const VAddr mapping_start = Common::AlignUp((address), PageSize);
+ const VAddr mapping_end = Common::AlignDown((address) + size, PageSize);
+ const VAddr mapping_last = mapping_end - 1;
+ const size_t mapping_size = (mapping_start < mapping_end) ? (mapping_end - mapping_start) : 0;
+
+ // If nothing was mapped, we're actually done immediately.
+ R_SUCCEED_IF(mapping_size == 0);
+
+ // Get the test state and attribute mask.
+ KMemoryState test_state;
+ KMemoryAttribute test_attr_mask;
+ switch (dst_state) {
+ case KMemoryState::Ipc:
+ test_state = KMemoryState::FlagCanUseIpc;
+ test_attr_mask =
+ KMemoryAttribute::Uncached | KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonSecureIpc:
+ test_state = KMemoryState::FlagCanUseNonSecureIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ case KMemoryState::NonDeviceIpc:
+ test_state = KMemoryState::FlagCanUseNonDeviceIpc;
+ test_attr_mask = KMemoryAttribute::Uncached | KMemoryAttribute::Locked;
+ break;
+ default:
+ R_THROW(ResultInvalidCombination);
+ }
+
+ // Lock the table.
+ // NOTE: Nintendo does this *after* creating the updater below, but this does not follow
+ // convention elsewhere in KPageTable.
+ KScopedLightLock lk(m_general_lock);
+
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Ensure that on failure, we roll back appropriately.
+ size_t mapped_size = 0;
+ ON_RESULT_FAILURE {
+ if (mapped_size > 0) {
+ // Determine where the mapping ends.
+ const auto mapped_end = (mapping_start) + mapped_size;
+ const auto mapped_last = mapped_end - 1;
+
+ // Get current and next iterators.
+ KMemoryBlockManager::const_iterator start_it =
+ m_memory_block_manager.FindIterator(mapping_start);
+ KMemoryBlockManager::const_iterator next_it = start_it;
+ ++next_it;
+
+ // Get the current block info.
+ KMemoryInfo cur_info = start_it->GetMemoryInfo();
+
+ // Create tracking variables.
+ VAddr cur_address = cur_info.GetAddress();
+ size_t cur_size = cur_info.GetSize();
+ bool cur_perm_eq = cur_info.GetPermission() == cur_info.GetOriginalPermission();
+ bool cur_needs_set_perm = !cur_perm_eq && cur_info.GetIpcLockCount() == 1;
+ bool first =
+ cur_info.GetIpcDisableMergeCount() == 1 &&
+ (cur_info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Locked) ==
+ KMemoryBlockDisableMergeAttribute::None;
+
+ while (((cur_address) + cur_size - 1) < mapped_last) {
+ // Check that we have a next block.
+ ASSERT(next_it != m_memory_block_manager.end());
+
+ // Get the next info.
+ const KMemoryInfo next_info = next_it->GetMemoryInfo();
+
+ // Check if we can consolidate the next block's permission set with the current one.
+
+ const bool next_perm_eq =
+ next_info.GetPermission() == next_info.GetOriginalPermission();
+ const bool next_needs_set_perm = !next_perm_eq && next_info.GetIpcLockCount() == 1;
+ if (cur_perm_eq == next_perm_eq && cur_needs_set_perm == next_needs_set_perm &&
+ cur_info.GetOriginalPermission() == next_info.GetOriginalPermission()) {
+ // We can consolidate the reprotection for the current and next block into a
+ // single call.
+ cur_size += next_info.GetSize();
+ } else {
+ // We have to operate on the current block.
+ if ((cur_needs_set_perm || first) && !cur_perm_eq) {
+ ASSERT(Operate(cur_address, cur_size / PageSize, cur_info.GetPermission(),
+ OperationType::ChangePermissions)
+ .IsSuccess());
+ }
+
+ // Advance.
+ cur_address = next_info.GetAddress();
+ cur_size = next_info.GetSize();
+ first = false;
+ }
+
+ // Advance.
+ cur_info = next_info;
+ cur_perm_eq = next_perm_eq;
+ cur_needs_set_perm = next_needs_set_perm;
+ ++next_it;
+ }
+
+ // Process the last block.
+ if ((first || cur_needs_set_perm) && !cur_perm_eq) {
+ ASSERT(Operate(cur_address, cur_size / PageSize, cur_info.GetPermission(),
+ OperationType::ChangePermissions)
+ .IsSuccess());
+ }
+ }
+ };
+
+ // Iterate, reprotecting as needed.
+ {
+ // Get current and next iterators.
+ KMemoryBlockManager::const_iterator start_it =
+ m_memory_block_manager.FindIterator(mapping_start);
+ KMemoryBlockManager::const_iterator next_it = start_it;
+ ++next_it;
+
+ // Validate the current block.
+ KMemoryInfo cur_info = start_it->GetMemoryInfo();
+ ASSERT(this->CheckMemoryState(cur_info, test_state, test_state, KMemoryPermission::None,
+ KMemoryPermission::None,
+ test_attr_mask | KMemoryAttribute::IpcLocked,
+ KMemoryAttribute::IpcLocked)
+ .IsSuccess());
+
+ // Create tracking variables.
+ VAddr cur_address = cur_info.GetAddress();
+ size_t cur_size = cur_info.GetSize();
+ bool cur_perm_eq = cur_info.GetPermission() == cur_info.GetOriginalPermission();
+ bool cur_needs_set_perm = !cur_perm_eq && cur_info.GetIpcLockCount() == 1;
+ bool first =
+ cur_info.GetIpcDisableMergeCount() == 1 &&
+ (cur_info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Locked) ==
+ KMemoryBlockDisableMergeAttribute::None;
+
+ while ((cur_address + cur_size - 1) < mapping_last) {
+ // Check that we have a next block.
+ ASSERT(next_it != m_memory_block_manager.end());
+
+ // Get the next info.
+ const KMemoryInfo next_info = next_it->GetMemoryInfo();
+
+ // Validate the next block.
+ ASSERT(this->CheckMemoryState(next_info, test_state, test_state,
+ KMemoryPermission::None, KMemoryPermission::None,
+ test_attr_mask | KMemoryAttribute::IpcLocked,
+ KMemoryAttribute::IpcLocked)
+ .IsSuccess());
+
+ // Check if we can consolidate the next block's permission set with the current one.
+ const bool next_perm_eq =
+ next_info.GetPermission() == next_info.GetOriginalPermission();
+ const bool next_needs_set_perm = !next_perm_eq && next_info.GetIpcLockCount() == 1;
+ if (cur_perm_eq == next_perm_eq && cur_needs_set_perm == next_needs_set_perm &&
+ cur_info.GetOriginalPermission() == next_info.GetOriginalPermission()) {
+ // We can consolidate the reprotection for the current and next block into a single
+ // call.
+ cur_size += next_info.GetSize();
+ } else {
+ // We have to operate on the current block.
+ if ((cur_needs_set_perm || first) && !cur_perm_eq) {
+ R_TRY(Operate(cur_address, cur_size / PageSize,
+ cur_needs_set_perm ? cur_info.GetOriginalPermission()
+ : cur_info.GetPermission(),
+ OperationType::ChangePermissions));
+ }
+
+ // Mark that we mapped the block.
+ mapped_size += cur_size;
+
+ // Advance.
+ cur_address = next_info.GetAddress();
+ cur_size = next_info.GetSize();
+ first = false;
+ }
+
+ // Advance.
+ cur_info = next_info;
+ cur_perm_eq = next_perm_eq;
+ cur_needs_set_perm = next_needs_set_perm;
+ ++next_it;
+ }
+
+ // Process the last block.
+ const auto lock_count =
+ cur_info.GetIpcLockCount() +
+ (next_it != m_memory_block_manager.end()
+ ? (next_it->GetIpcDisableMergeCount() - next_it->GetIpcLockCount())
+ : 0);
+ if ((first || cur_needs_set_perm || (lock_count == 1)) && !cur_perm_eq) {
+ R_TRY(Operate(cur_address, cur_size / PageSize,
+ cur_needs_set_perm ? cur_info.GetOriginalPermission()
+ : cur_info.GetPermission(),
+ OperationType::ChangePermissions));
+ }
+ }
+
+ // Create an update allocator.
+ // NOTE: Guaranteed zero blocks needed here.
+ Result allocator_result;
+ KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
+ m_memory_block_slab_manager, 0);
+ R_TRY(allocator_result);
+
+ // Unlock the pages.
+ m_memory_block_manager.UpdateLock(std::addressof(allocator), mapping_start,
+ mapping_size / PageSize, &KMemoryBlock::UnlockForIpc,
+ KMemoryPermission::None);
+
+ R_SUCCEED();
+}
+
+void KPageTable::CleanupForIpcClientOnServerSetupFailure([[maybe_unused]] PageLinkedList* page_list,
+ VAddr address, size_t size,
+ KMemoryPermission prot_perm) {
+ ASSERT(this->IsLockedByCurrentThread());
+ ASSERT(Common::IsAligned(address, PageSize));
+ ASSERT(Common::IsAligned(size, PageSize));
+
+ // Get the mapped extents.
+ const VAddr src_map_start = address;
+ const VAddr src_map_end = address + size;
+ const VAddr src_map_last = src_map_end - 1;
+
+ // This function is only invoked when there's something to do.
+ ASSERT(src_map_end > src_map_start);
+
+ // Iterate over blocks, fixing permissions.
+ KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(address);
+ while (true) {
+ const KMemoryInfo info = it->GetMemoryInfo();
+
+ const auto cur_start =
+ info.GetAddress() >= src_map_start ? info.GetAddress() : src_map_start;
+ const auto cur_end =
+ src_map_last <= info.GetLastAddress() ? src_map_end : info.GetEndAddress();
+
+ // If we can, fix the protections on the block.
+ if ((info.GetIpcLockCount() == 0 &&
+ (info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm) ||
+ (info.GetIpcLockCount() != 0 &&
+ (info.GetOriginalPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm)) {
+ // Check if we actually need to fix the protections on the block.
+ if (cur_end == src_map_end || info.GetAddress() <= src_map_start ||
+ (info.GetPermission() & KMemoryPermission::IpcLockChangeMask) != prot_perm) {
+ ASSERT(Operate(cur_start, (cur_end - cur_start) / PageSize, info.GetPermission(),
+ OperationType::ChangePermissions)
+ .IsSuccess());
+ }
+ }
+
+ // If we're past the end of the region, we're done.
+ if (src_map_last <= info.GetLastAddress()) {
+ break;
+ }
+
+ // Advance.
+ ++it;
+ ASSERT(it != m_memory_block_manager.end());
+ }
+}
+
+void KPageTable::HACK_OpenPages(PAddr phys_addr, size_t num_pages) {
+ m_system.Kernel().MemoryManager().OpenFirst(phys_addr, num_pages);
+}
+
+void KPageTable::HACK_ClosePages(VAddr virt_addr, size_t num_pages) {
+ for (size_t index = 0; index < num_pages; ++index) {
+ const auto paddr = GetPhysicalAddr(virt_addr + (index * PageSize));
+ m_system.Kernel().MemoryManager().Close(paddr, 1);
+ }
+}
+
Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Lock the physical memory lock.
- KScopedLightLock map_phys_mem_lk(m_map_physical_memory_lock);
+ KScopedLightLock phys_lk(m_map_physical_memory_lock);
// Calculate the last address for convenience.
const VAddr last_address = address + size - 1;
@@ -742,15 +1568,19 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
{
// Reserve the memory from the process resource limit.
KScopedResourceReservation memory_reservation(
- m_system.Kernel().CurrentProcess()->GetResourceLimit(),
- LimitableResource::PhysicalMemory, size - mapped_size);
+ m_resource_limit, LimitableResource::PhysicalMemoryMax, size - mapped_size);
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
// Allocate pages for the new memory.
KPageGroup pg;
- R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpenForProcess(
- &pg, (size - mapped_size) / PageSize,
- KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option), 0, 0));
+ R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess(
+ &pg, (size - mapped_size) / PageSize, m_allocate_option, 0, 0));
+
+ // If we fail in the next bit (or retry), we need to cleanup the pages.
+ // auto pg_guard = SCOPE_GUARD {
+ // pg.OpenFirst();
+ // pg.Close();
+ //};
// Map the memory.
{
@@ -810,15 +1640,24 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Create an update allocator.
ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
- Result allocator_result{ResultSuccess};
+ Result allocator_result;
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
m_memory_block_slab_manager,
num_allocator_blocks);
R_TRY(allocator_result);
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
+
+ // Prepare to iterate over the memory.
+ auto pg_it = pg.Nodes().begin();
+ PAddr pg_phys_addr = pg_it->GetAddress();
+ size_t pg_pages = pg_it->GetNumPages();
+
// Reset the current tracking address, and make sure we clean up on failure.
+ // pg_guard.Cancel();
cur_address = address;
- auto unmap_guard = detail::ScopeExit([&] {
+ ON_RESULT_FAILURE {
if (cur_address > address) {
const VAddr last_unmap_address = cur_address - 1;
@@ -841,6 +1680,9 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
last_unmap_address + 1 - cur_address) /
PageSize;
+ // HACK: Manually close the pages.
+ HACK_ClosePages(cur_address, cur_pages);
+
// Unmap.
ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None,
OperationType::Unmap)
@@ -857,12 +1699,17 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
++it;
}
}
- });
- // Iterate over the memory.
- auto pg_it = pg.Nodes().begin();
- PAddr pg_phys_addr = pg_it->GetAddress();
- size_t pg_pages = pg_it->GetNumPages();
+ // Release any remaining unmapped memory.
+ m_system.Kernel().MemoryManager().OpenFirst(pg_phys_addr, pg_pages);
+ m_system.Kernel().MemoryManager().Close(pg_phys_addr, pg_pages);
+ for (++pg_it; pg_it != pg.Nodes().end(); ++pg_it) {
+ m_system.Kernel().MemoryManager().OpenFirst(pg_it->GetAddress(),
+ pg_it->GetNumPages());
+ m_system.Kernel().MemoryManager().Close(pg_it->GetAddress(),
+ pg_it->GetNumPages());
+ }
+ };
auto it = m_memory_block_manager.FindIterator(cur_address);
while (true) {
@@ -897,6 +1744,9 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::UserReadWrite,
OperationType::Map, pg_phys_addr));
+ // HACK: Manually open the pages.
+ HACK_OpenPages(pg_phys_addr, cur_pages);
+
// Advance.
cur_address += cur_pages * PageSize;
map_pages -= cur_pages;
@@ -928,9 +1778,6 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
KMemoryPermission::None, KMemoryAttribute::None, KMemoryState::Normal,
KMemoryPermission::UserReadWrite, KMemoryAttribute::None);
- // Cancel our guard.
- unmap_guard.Cancel();
-
R_SUCCEED();
}
}
@@ -939,7 +1786,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
// Lock the physical memory lock.
- KScopedLightLock map_phys_mem_lk(m_map_physical_memory_lock);
+ KScopedLightLock phys_lk(m_map_physical_memory_lock);
// Lock the table.
KScopedLightLock lk(m_general_lock);
@@ -948,8 +1795,11 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
const VAddr last_address = address + size - 1;
// Define iteration variables.
- VAddr cur_address = 0;
- size_t mapped_size = 0;
+ VAddr map_start_address = 0;
+ VAddr map_last_address = 0;
+
+ VAddr cur_address;
+ size_t mapped_size;
size_t num_allocator_blocks = 0;
// Check if the memory is mapped.
@@ -975,27 +1825,27 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
if (is_normal) {
R_UNLESS(info.GetAttribute() == KMemoryAttribute::None, ResultInvalidCurrentMemory);
+ if (map_start_address == 0) {
+ map_start_address = cur_address;
+ }
+ map_last_address =
+ (last_address >= info.GetLastAddress()) ? info.GetLastAddress() : last_address;
+
if (info.GetAddress() < address) {
++num_allocator_blocks;
}
if (last_address < info.GetLastAddress()) {
++num_allocator_blocks;
}
+
+ mapped_size += (map_last_address + 1 - cur_address);
}
// Check if we're done.
if (last_address <= info.GetLastAddress()) {
- if (is_normal) {
- mapped_size += (last_address + 1 - cur_address);
- }
break;
}
- // Track the memory if it's mapped.
- if (is_normal) {
- mapped_size += VAddr(info.GetEndAddress()) - cur_address;
- }
-
// Advance.
cur_address = info.GetEndAddress();
++it;
@@ -1005,125 +1855,22 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
R_SUCCEED_IF(mapped_size == 0);
}
- // Make a page group for the unmap region.
- KPageGroup pg;
- {
- auto& impl = this->PageTableImpl();
-
- // Begin traversal.
- Common::PageTable::TraversalContext context;
- Common::PageTable::TraversalEntry cur_entry = {.phys_addr = 0, .block_size = 0};
- bool cur_valid = false;
- Common::PageTable::TraversalEntry next_entry;
- bool next_valid = false;
- size_t tot_size = 0;
-
- cur_address = address;
- next_valid = impl.BeginTraversal(next_entry, context, cur_address);
- next_entry.block_size =
- (next_entry.block_size - (next_entry.phys_addr & (next_entry.block_size - 1)));
-
- // Iterate, building the group.
- while (true) {
- if ((!next_valid && !cur_valid) ||
- (next_valid && cur_valid &&
- next_entry.phys_addr == cur_entry.phys_addr + cur_entry.block_size)) {
- cur_entry.block_size += next_entry.block_size;
- } else {
- if (cur_valid) {
- // ASSERT(IsHeapPhysicalAddress(cur_entry.phys_addr));
- R_TRY(pg.AddBlock(cur_entry.phys_addr, cur_entry.block_size / PageSize));
- }
-
- // Update tracking variables.
- tot_size += cur_entry.block_size;
- cur_entry = next_entry;
- cur_valid = next_valid;
- }
-
- if (cur_entry.block_size + tot_size >= size) {
- break;
- }
-
- next_valid = impl.ContinueTraversal(next_entry, context);
- }
-
- // Add the last block.
- if (cur_valid) {
- // ASSERT(IsHeapPhysicalAddress(cur_entry.phys_addr));
- R_TRY(pg.AddBlock(cur_entry.phys_addr, (size - tot_size) / PageSize));
- }
- }
- ASSERT(pg.GetNumPages() == mapped_size / PageSize);
-
// Create an update allocator.
ASSERT(num_allocator_blocks <= KMemoryBlockManagerUpdateAllocator::MaxBlocks);
- Result allocator_result{ResultSuccess};
+ Result allocator_result;
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
m_memory_block_slab_manager, num_allocator_blocks);
R_TRY(allocator_result);
- // Reset the current tracking address, and make sure we clean up on failure.
- cur_address = address;
- auto remap_guard = detail::ScopeExit([&] {
- if (cur_address > address) {
- const VAddr last_map_address = cur_address - 1;
- cur_address = address;
-
- // Iterate over the memory we unmapped.
- auto it = m_memory_block_manager.FindIterator(cur_address);
- auto pg_it = pg.Nodes().begin();
- PAddr pg_phys_addr = pg_it->GetAddress();
- size_t pg_pages = pg_it->GetNumPages();
-
- while (true) {
- // Get the memory info for the pages we unmapped, convert to property.
- const KMemoryInfo info = it->GetMemoryInfo();
-
- // If the memory is normal, we unmapped it and need to re-map it.
- if (info.GetState() == KMemoryState::Normal) {
- // Determine the range to map.
- size_t map_pages = std::min(VAddr(info.GetEndAddress()) - cur_address,
- last_map_address + 1 - cur_address) /
- PageSize;
-
- // While we have pages to map, map them.
- while (map_pages > 0) {
- // Check if we're at the end of the physical block.
- if (pg_pages == 0) {
- // Ensure there are more pages to map.
- ASSERT(pg_it != pg.Nodes().end());
-
- // Advance our physical block.
- ++pg_it;
- pg_phys_addr = pg_it->GetAddress();
- pg_pages = pg_it->GetNumPages();
- }
-
- // Map whatever we can.
- const size_t cur_pages = std::min(pg_pages, map_pages);
- ASSERT(this->Operate(cur_address, cur_pages, info.GetPermission(),
- OperationType::Map, pg_phys_addr) == ResultSuccess);
+ // We're going to perform an update, so create a helper.
+ KScopedPageTableUpdater updater(this);
- // Advance.
- cur_address += cur_pages * PageSize;
- map_pages -= cur_pages;
+ // Separate the mapping.
+ R_TRY(Operate(map_start_address, (map_last_address + 1 - map_start_address) / PageSize,
+ KMemoryPermission::None, OperationType::Separate));
- pg_phys_addr += cur_pages * PageSize;
- pg_pages -= cur_pages;
- }
- }
-
- // Check if we're done.
- if (last_map_address <= info.GetLastAddress()) {
- break;
- }
-
- // Advance.
- ++it;
- }
- }
- });
+ // Reset the current tracking address, and make sure we clean up on failure.
+ cur_address = address;
// Iterate over the memory, unmapping as we go.
auto it = m_memory_block_manager.FindIterator(cur_address);
@@ -1141,8 +1888,12 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
last_address + 1 - cur_address) /
PageSize;
+ // HACK: Manually close the pages.
+ HACK_ClosePages(cur_address, cur_pages);
+
// Unmap.
- R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::None, OperationType::Unmap));
+ ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None, OperationType::Unmap)
+ .IsSuccess());
}
// Check if we're done.
@@ -1157,8 +1908,7 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
// Release the memory resource.
m_mapped_physical_memory_size -= mapped_size;
- auto process{m_system.Kernel().CurrentProcess()};
- process->GetResourceLimit()->Release(LimitableResource::PhysicalMemory, mapped_size);
+ m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, mapped_size);
// Update memory blocks.
m_memory_block_manager.Update(std::addressof(allocator), address, size / PageSize,
@@ -1166,14 +1916,7 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None,
KMemoryBlockDisableMergeAttribute::None);
- // TODO(bunnei): This is a workaround until the next set of changes, where we add reference
- // counting for mapped pages. Until then, we must manually close the reference to the page
- // group.
- m_system.Kernel().MemoryManager().Close(pg);
-
// We succeeded.
- remap_guard.Cancel();
-
R_SUCCEED();
}
@@ -1749,8 +2492,7 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) {
OperationType::Unmap));
// Release the memory from the resource limit.
- m_system.Kernel().CurrentProcess()->GetResourceLimit()->Release(
- LimitableResource::PhysicalMemory, num_pages * PageSize);
+ m_resource_limit->Release(LimitableResource::PhysicalMemoryMax, num_pages * PageSize);
// Apply the memory block update.
m_memory_block_manager.Update(std::addressof(allocator), m_heap_region_start + size,
@@ -1780,8 +2522,7 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) {
// Reserve memory for the heap extension.
KScopedResourceReservation memory_reservation(
- m_system.Kernel().CurrentProcess()->GetResourceLimit(), LimitableResource::PhysicalMemory,
- allocation_size);
+ m_resource_limit, LimitableResource::PhysicalMemoryMax, allocation_size);
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
// Allocate pages for the heap extension.
@@ -1869,7 +2610,7 @@ ResultVal<VAddr> KPageTable::AllocateAndMapMemory(size_t needed_num_pages, size_
R_TRY(Operate(addr, needed_num_pages, perm, OperationType::Map, map_addr));
} else {
KPageGroup page_group;
- R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpenForProcess(
+ R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess(
&page_group, needed_num_pages,
KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option), 0, 0));
R_TRY(Operate(addr, needed_num_pages, page_group, OperationType::MapGroup));
@@ -1883,8 +2624,9 @@ ResultVal<VAddr> KPageTable::AllocateAndMapMemory(size_t needed_num_pages, size_
return addr;
}
-Result KPageTable::LockForMapDeviceAddressSpace(VAddr address, size_t size, KMemoryPermission perm,
- bool is_aligned) {
+Result KPageTable::LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size,
+ KMemoryPermission perm, bool is_aligned,
+ bool check_heap) {
// Lightly validate the range before doing anything else.
const size_t num_pages = size / PageSize;
R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
@@ -1894,15 +2636,18 @@ Result KPageTable::LockForMapDeviceAddressSpace(VAddr address, size_t size, KMem
// Check the memory state.
const auto test_state =
- (is_aligned ? KMemoryState::FlagCanAlignedDeviceMap : KMemoryState::FlagCanDeviceMap);
+ (is_aligned ? KMemoryState::FlagCanAlignedDeviceMap : KMemoryState::FlagCanDeviceMap) |
+ (check_heap ? KMemoryState::FlagReferenceCounted : KMemoryState::None);
size_t num_allocator_blocks;
- R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, test_state,
+ KMemoryState old_state;
+ R_TRY(this->CheckMemoryState(std::addressof(old_state), nullptr, nullptr,
+ std::addressof(num_allocator_blocks), address, size, test_state,
test_state, perm, perm,
KMemoryAttribute::IpcLocked | KMemoryAttribute::Locked,
KMemoryAttribute::None, KMemoryAttribute::DeviceShared));
// Create an update allocator.
- Result allocator_result{ResultSuccess};
+ Result allocator_result;
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
m_memory_block_slab_manager, num_allocator_blocks);
R_TRY(allocator_result);
@@ -1911,10 +2656,13 @@ Result KPageTable::LockForMapDeviceAddressSpace(VAddr address, size_t size, KMem
m_memory_block_manager.UpdateLock(std::addressof(allocator), address, num_pages,
&KMemoryBlock::ShareToDevice, KMemoryPermission::None);
+ // Set whether the locked memory was io.
+ *out_is_io = old_state == KMemoryState::Io;
+
R_SUCCEED();
}
-Result KPageTable::LockForUnmapDeviceAddressSpace(VAddr address, size_t size) {
+Result KPageTable::LockForUnmapDeviceAddressSpace(VAddr address, size_t size, bool check_heap) {
// Lightly validate the range before doing anything else.
const size_t num_pages = size / PageSize;
R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory);
@@ -1923,16 +2671,16 @@ Result KPageTable::LockForUnmapDeviceAddressSpace(VAddr address, size_t size) {
KScopedLightLock lk(m_general_lock);
// Check the memory state.
+ const auto test_state = KMemoryState::FlagCanDeviceMap |
+ (check_heap ? KMemoryState::FlagReferenceCounted : KMemoryState::None);
size_t num_allocator_blocks;
R_TRY(this->CheckMemoryStateContiguous(
- std::addressof(num_allocator_blocks), address, size,
- KMemoryState::FlagReferenceCounted | KMemoryState::FlagCanDeviceMap,
- KMemoryState::FlagReferenceCounted | KMemoryState::FlagCanDeviceMap,
+ std::addressof(num_allocator_blocks), address, size, test_state, test_state,
KMemoryPermission::None, KMemoryPermission::None,
KMemoryAttribute::DeviceShared | KMemoryAttribute::Locked, KMemoryAttribute::DeviceShared));
// Create an update allocator.
- Result allocator_result{ResultSuccess};
+ Result allocator_result;
KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result),
m_memory_block_slab_manager, num_allocator_blocks);
R_TRY(allocator_result);
@@ -1976,13 +2724,28 @@ Result KPageTable::UnlockForDeviceAddressSpace(VAddr address, size_t size) {
R_SUCCEED();
}
+Result KPageTable::LockForIpcUserBuffer(PAddr* out, VAddr address, size_t size) {
+ R_RETURN(this->LockMemoryAndOpen(
+ nullptr, out, address, size, KMemoryState::FlagCanIpcUserBuffer,
+ KMemoryState::FlagCanIpcUserBuffer, KMemoryPermission::All,
+ KMemoryPermission::UserReadWrite, KMemoryAttribute::All, KMemoryAttribute::None,
+ KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite,
+ KMemoryAttribute::Locked));
+}
+
+Result KPageTable::UnlockForIpcUserBuffer(VAddr address, size_t size) {
+ R_RETURN(this->UnlockMemory(address, size, KMemoryState::FlagCanIpcUserBuffer,
+ KMemoryState::FlagCanIpcUserBuffer, KMemoryPermission::None,
+ KMemoryPermission::None, KMemoryAttribute::All,
+ KMemoryAttribute::Locked, KMemoryPermission::UserReadWrite,
+ KMemoryAttribute::Locked, nullptr));
+}
+
Result KPageTable::LockForCodeMemory(KPageGroup* out, VAddr addr, size_t size) {
R_RETURN(this->LockMemoryAndOpen(
out, nullptr, addr, size, KMemoryState::FlagCanCodeMemory, KMemoryState::FlagCanCodeMemory,
KMemoryPermission::All, KMemoryPermission::UserReadWrite, KMemoryAttribute::All,
- KMemoryAttribute::None,
- static_cast<KMemoryPermission>(KMemoryPermission::NotMapped |
- KMemoryPermission::KernelReadWrite),
+ KMemoryAttribute::None, KMemoryPermission::NotMapped | KMemoryPermission::KernelReadWrite,
KMemoryAttribute::Locked));
}
@@ -2066,6 +2829,10 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm,
m_system.Memory().MapMemoryRegion(*m_page_table_impl, addr, num_pages * PageSize, map_addr);
break;
}
+ case OperationType::Separate: {
+ // HACK: Unimplemented.
+ break;
+ }
case OperationType::ChangePermissions:
case OperationType::ChangePermissionsAndRefresh:
break;
@@ -2075,6 +2842,17 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm,
R_SUCCEED();
}
+void KPageTable::FinalizeUpdate(PageLinkedList* page_list) {
+ while (page_list->Peek()) {
+ [[maybe_unused]] auto page = page_list->Pop();
+
+ // TODO(bunnei): Free pages once they are allocated in guest memory
+ // ASSERT(this->GetPageTableManager().IsInPageTableHeap(page));
+ // ASSERT(this->GetPageTableManager().GetRefCount(page) == 0);
+ // this->GetPageTableManager().Free(page);
+ }
+}
+
VAddr KPageTable::GetRegionAddress(KMemoryState state) const {
switch (state) {
case KMemoryState::Free:
@@ -2101,6 +2879,7 @@ VAddr KPageTable::GetRegionAddress(KMemoryState state) const {
case KMemoryState::GeneratedCode:
case KMemoryState::CodeOut:
case KMemoryState::Coverage:
+ case KMemoryState::Insecure:
return m_alias_code_region_start;
case KMemoryState::Code:
case KMemoryState::CodeData:
@@ -2136,6 +2915,7 @@ size_t KPageTable::GetRegionSize(KMemoryState state) const {
case KMemoryState::GeneratedCode:
case KMemoryState::CodeOut:
case KMemoryState::Coverage:
+ case KMemoryState::Insecure:
return m_alias_code_region_end - m_alias_code_region_start;
case KMemoryState::Code:
case KMemoryState::CodeData:
@@ -2177,6 +2957,7 @@ bool KPageTable::CanContain(VAddr addr, size_t size, KMemoryState state) const {
case KMemoryState::GeneratedCode:
case KMemoryState::CodeOut:
case KMemoryState::Coverage:
+ case KMemoryState::Insecure:
return is_in_region && !is_in_heap && !is_in_alias;
case KMemoryState::Normal:
ASSERT(is_in_heap);
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index c6aeacd96..950850291 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -16,6 +16,7 @@
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/result.h"
+#include "core/memory.h"
namespace Core {
class System;
@@ -23,7 +24,10 @@ class System;
namespace Kernel {
+class KBlockInfoManager;
class KMemoryBlockManager;
+class KResourceLimit;
+class KSystemResource;
class KPageTable final {
public:
@@ -36,9 +40,9 @@ public:
~KPageTable();
Result InitializeForProcess(FileSys::ProgramAddressSpaceType as_type, bool enable_aslr,
- VAddr code_addr, size_t code_size,
- KMemoryBlockSlabManager* mem_block_slab_manager,
- KMemoryManager::Pool pool);
+ bool enable_das_merge, bool from_back, KMemoryManager::Pool pool,
+ VAddr code_addr, size_t code_size, KSystemResource* system_resource,
+ KResourceLimit* resource_limit);
void Finalize();
@@ -74,12 +78,20 @@ public:
KMemoryState state, KMemoryPermission perm,
PAddr map_addr = 0);
- Result LockForMapDeviceAddressSpace(VAddr address, size_t size, KMemoryPermission perm,
- bool is_aligned);
- Result LockForUnmapDeviceAddressSpace(VAddr address, size_t size);
+ Result LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size,
+ KMemoryPermission perm, bool is_aligned, bool check_heap);
+ Result LockForUnmapDeviceAddressSpace(VAddr address, size_t size, bool check_heap);
Result UnlockForDeviceAddressSpace(VAddr addr, size_t size);
+ Result LockForIpcUserBuffer(PAddr* out, VAddr address, size_t size);
+ Result UnlockForIpcUserBuffer(VAddr address, size_t size);
+
+ Result SetupForIpc(VAddr* out_dst_addr, size_t size, VAddr src_addr, KPageTable& src_page_table,
+ KMemoryPermission test_perm, KMemoryState dst_state, bool send);
+ Result CleanupForIpcServer(VAddr address, size_t size, KMemoryState dst_state);
+ Result CleanupForIpcClient(VAddr address, size_t size, KMemoryState dst_state);
+
Result LockForCodeMemory(KPageGroup* out, VAddr addr, size_t size);
Result UnlockForCodeMemory(VAddr addr, size_t size, const KPageGroup& pg);
Result MakeAndOpenPageGroup(KPageGroup* out, VAddr address, size_t num_pages,
@@ -97,13 +109,54 @@ public:
bool CanContain(VAddr addr, size_t size, KMemoryState state) const;
+protected:
+ struct PageLinkedList {
+ private:
+ struct Node {
+ Node* m_next;
+ std::array<u8, PageSize - sizeof(Node*)> m_buffer;
+ };
+
+ public:
+ constexpr PageLinkedList() = default;
+
+ void Push(Node* n) {
+ ASSERT(Common::IsAligned(reinterpret_cast<uintptr_t>(n), PageSize));
+ n->m_next = m_root;
+ m_root = n;
+ }
+
+ void Push(Core::Memory::Memory& memory, VAddr addr) {
+ this->Push(memory.GetPointer<Node>(addr));
+ }
+
+ Node* Peek() const {
+ return m_root;
+ }
+
+ Node* Pop() {
+ Node* const r = m_root;
+
+ m_root = r->m_next;
+ r->m_next = nullptr;
+
+ return r;
+ }
+
+ private:
+ Node* m_root{};
+ };
+ static_assert(std::is_trivially_destructible<PageLinkedList>::value);
+
private:
enum class OperationType : u32 {
- Map,
- MapGroup,
- Unmap,
- ChangePermissions,
- ChangePermissionsAndRefresh,
+ Map = 0,
+ MapFirst = 1,
+ MapGroup = 2,
+ Unmap = 3,
+ ChangePermissions = 4,
+ ChangePermissionsAndRefresh = 5,
+ Separate = 6,
};
static constexpr KMemoryAttribute DefaultMemoryIgnoreAttr =
@@ -123,6 +176,7 @@ private:
OperationType operation);
Result Operate(VAddr addr, size_t num_pages, KMemoryPermission perm, OperationType operation,
PAddr map_addr = 0);
+ void FinalizeUpdate(PageLinkedList* page_list);
VAddr GetRegionAddress(KMemoryState state) const;
size_t GetRegionSize(KMemoryState state) const;
@@ -199,6 +253,18 @@ private:
return *out != 0;
}
+ Result SetupForIpcClient(PageLinkedList* page_list, size_t* out_blocks_needed, VAddr address,
+ size_t size, KMemoryPermission test_perm, KMemoryState dst_state);
+ Result SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_addr,
+ KMemoryPermission test_perm, KMemoryState dst_state,
+ KPageTable& src_page_table, bool send);
+ void CleanupForIpcClientOnServerSetupFailure(PageLinkedList* page_list, VAddr address,
+ size_t size, KMemoryPermission prot_perm);
+
+ // HACK: These will be removed once we automatically manage page reference counts.
+ void HACK_OpenPages(PAddr phys_addr, size_t num_pages);
+ void HACK_ClosePages(VAddr virt_addr, size_t num_pages);
+
mutable KLightLock m_general_lock;
mutable KLightLock m_map_physical_memory_lock;
@@ -316,6 +382,31 @@ public:
addr + size - 1 <= m_address_space_end - 1;
}
+public:
+ static VAddr GetLinearMappedVirtualAddress(const KMemoryLayout& layout, PAddr addr) {
+ return layout.GetLinearVirtualAddress(addr);
+ }
+
+ static PAddr GetLinearMappedPhysicalAddress(const KMemoryLayout& layout, VAddr addr) {
+ return layout.GetLinearPhysicalAddress(addr);
+ }
+
+ static VAddr GetHeapVirtualAddress(const KMemoryLayout& layout, PAddr addr) {
+ return GetLinearMappedVirtualAddress(layout, addr);
+ }
+
+ static PAddr GetHeapPhysicalAddress(const KMemoryLayout& layout, VAddr addr) {
+ return GetLinearMappedPhysicalAddress(layout, addr);
+ }
+
+ static VAddr GetPageTableVirtualAddress(const KMemoryLayout& layout, PAddr addr) {
+ return GetLinearMappedVirtualAddress(layout, addr);
+ }
+
+ static PAddr GetPageTablePhysicalAddress(const KMemoryLayout& layout, VAddr addr) {
+ return GetLinearMappedPhysicalAddress(layout, addr);
+ }
+
private:
constexpr bool IsKernel() const {
return m_is_kernel;
@@ -331,6 +422,24 @@ private:
}
private:
+ class KScopedPageTableUpdater {
+ private:
+ KPageTable* m_pt{};
+ PageLinkedList m_ll;
+
+ public:
+ explicit KScopedPageTableUpdater(KPageTable* pt) : m_pt(pt) {}
+ explicit KScopedPageTableUpdater(KPageTable& pt) : KScopedPageTableUpdater(&pt) {}
+ ~KScopedPageTableUpdater() {
+ m_pt->FinalizeUpdate(this->GetPageList());
+ }
+
+ PageLinkedList* GetPageList() {
+ return &m_ll;
+ }
+ };
+
+private:
VAddr m_address_space_start{};
VAddr m_address_space_end{};
VAddr m_heap_region_start{};
@@ -347,20 +456,27 @@ private:
VAddr m_alias_code_region_start{};
VAddr m_alias_code_region_end{};
- size_t m_mapped_physical_memory_size{};
size_t m_max_heap_size{};
- size_t m_max_physical_memory_size{};
+ size_t m_mapped_physical_memory_size{};
+ size_t m_mapped_unsafe_physical_memory{};
+ size_t m_mapped_insecure_memory{};
+ size_t m_mapped_ipc_server_memory{};
size_t m_address_space_width{};
KMemoryBlockManager m_memory_block_manager;
+ u32 m_allocate_option{};
bool m_is_kernel{};
bool m_enable_aslr{};
bool m_enable_device_address_space_merge{};
KMemoryBlockSlabManager* m_memory_block_slab_manager{};
+ KBlockInfoManager* m_block_info_manager{};
+ KResourceLimit* m_resource_limit{};
u32 m_heap_fill_value{};
+ u32 m_ipc_fill_value{};
+ u32 m_stack_fill_value{};
const KMemoryRegion* m_cached_physical_heap_region{};
KMemoryManager::Pool m_memory_pool{KMemoryManager::Pool::Application};
diff --git a/src/core/hle/kernel/k_page_table_manager.h b/src/core/hle/kernel/k_page_table_manager.h
new file mode 100644
index 000000000..91a45cde3
--- /dev/null
+++ b/src/core/hle/kernel/k_page_table_manager.h
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <atomic>
+
+#include "common/common_types.h"
+#include "core/hle/kernel/k_dynamic_resource_manager.h"
+#include "core/hle/kernel/k_page_table_slab_heap.h"
+
+namespace Kernel {
+
+class KPageTableManager : public KDynamicResourceManager<impl::PageTablePage, true> {
+public:
+ using RefCount = KPageTableSlabHeap::RefCount;
+ static constexpr size_t PageTableSize = KPageTableSlabHeap::PageTableSize;
+
+public:
+ KPageTableManager() = default;
+
+ void Initialize(KDynamicPageManager* page_allocator, KPageTableSlabHeap* pt_heap) {
+ m_pt_heap = pt_heap;
+
+ static_assert(std::derived_from<KPageTableSlabHeap, DynamicSlabType>);
+ BaseHeap::Initialize(page_allocator, pt_heap);
+ }
+
+ VAddr Allocate() {
+ return VAddr(BaseHeap::Allocate());
+ }
+
+ RefCount GetRefCount(VAddr addr) const {
+ return m_pt_heap->GetRefCount(addr);
+ }
+
+ void Open(VAddr addr, int count) {
+ return m_pt_heap->Open(addr, count);
+ }
+
+ bool Close(VAddr addr, int count) {
+ return m_pt_heap->Close(addr, count);
+ }
+
+ bool IsInPageTableHeap(VAddr addr) const {
+ return m_pt_heap->IsInRange(addr);
+ }
+
+private:
+ using BaseHeap = KDynamicResourceManager<impl::PageTablePage, true>;
+
+ KPageTableSlabHeap* m_pt_heap{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_table_slab_heap.h b/src/core/hle/kernel/k_page_table_slab_heap.h
new file mode 100644
index 000000000..a9543cbd0
--- /dev/null
+++ b/src/core/hle/kernel/k_page_table_slab_heap.h
@@ -0,0 +1,93 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_types.h"
+#include "core/hle/kernel/k_dynamic_slab_heap.h"
+#include "core/hle/kernel/slab_helpers.h"
+
+namespace Kernel {
+
+namespace impl {
+
+class PageTablePage {
+public:
+ // Do not initialize anything.
+ PageTablePage() = default;
+
+private:
+ std::array<u8, PageSize> m_buffer{};
+};
+static_assert(sizeof(PageTablePage) == PageSize);
+
+} // namespace impl
+
+class KPageTableSlabHeap : public KDynamicSlabHeap<impl::PageTablePage, true> {
+public:
+ using RefCount = u16;
+ static constexpr size_t PageTableSize = sizeof(impl::PageTablePage);
+ static_assert(PageTableSize == PageSize);
+
+public:
+ KPageTableSlabHeap() = default;
+
+ static constexpr size_t CalculateReferenceCountSize(size_t size) {
+ return (size / PageSize) * sizeof(RefCount);
+ }
+
+ void Initialize(KDynamicPageManager* page_allocator, size_t object_count, RefCount* rc) {
+ BaseHeap::Initialize(page_allocator, object_count);
+ this->Initialize(rc);
+ }
+
+ RefCount GetRefCount(VAddr addr) {
+ ASSERT(this->IsInRange(addr));
+ return *this->GetRefCountPointer(addr);
+ }
+
+ void Open(VAddr addr, int count) {
+ ASSERT(this->IsInRange(addr));
+
+ *this->GetRefCountPointer(addr) += static_cast<RefCount>(count);
+
+ ASSERT(this->GetRefCount(addr) > 0);
+ }
+
+ bool Close(VAddr addr, int count) {
+ ASSERT(this->IsInRange(addr));
+ ASSERT(this->GetRefCount(addr) >= count);
+
+ *this->GetRefCountPointer(addr) -= static_cast<RefCount>(count);
+ return this->GetRefCount(addr) == 0;
+ }
+
+ bool IsInPageTableHeap(VAddr addr) const {
+ return this->IsInRange(addr);
+ }
+
+private:
+ void Initialize([[maybe_unused]] RefCount* rc) {
+ // TODO(bunnei): Use rc once we support kernel virtual memory allocations.
+ const auto count = this->GetSize() / PageSize;
+ m_ref_counts.resize(count);
+
+ for (size_t i = 0; i < count; i++) {
+ m_ref_counts[i] = 0;
+ }
+ }
+
+ RefCount* GetRefCountPointer(VAddr addr) {
+ return m_ref_counts.data() + ((addr - this->GetAddress()) / PageSize);
+ }
+
+private:
+ using BaseHeap = KDynamicSlabHeap<impl::PageTablePage, true>;
+
+ std::vector<RefCount> m_ref_counts;
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_port.cpp b/src/core/hle/kernel/k_port.cpp
index 7a5a9dc2a..77d00ae2c 100644
--- a/src/core/hle/kernel/k_port.cpp
+++ b/src/core/hle/kernel/k_port.cpp
@@ -57,12 +57,6 @@ Result KPort::EnqueueSession(KServerSession* session) {
server.EnqueueSession(session);
- if (auto session_ptr = server.GetSessionRequestHandler().lock()) {
- session_ptr->ClientConnected(server.AcceptSession());
- } else {
- ASSERT(false);
- }
-
return ResultSuccess;
}
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index 8c3495e5a..55a9c5fae 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -38,7 +38,7 @@ namespace {
*/
void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority, VAddr stack_top) {
const VAddr entry_point = owner_process.PageTable().GetCodeRegionStart();
- ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::Threads, 1));
+ ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1));
KThread* thread = KThread::Create(system.Kernel());
SCOPE_EXIT({ thread->Close(); });
@@ -124,7 +124,7 @@ void KProcess::DecrementRunningThreadCount() {
}
u64 KProcess::GetTotalPhysicalMemoryAvailable() {
- const u64 capacity{resource_limit->GetFreeValue(LimitableResource::PhysicalMemory) +
+ const u64 capacity{resource_limit->GetFreeValue(LimitableResource::PhysicalMemoryMax) +
page_table.GetNormalMemorySize() + GetSystemResourceSize() + image_size +
main_thread_stack_size};
if (const auto pool_size = kernel.MemoryManager().GetSize(KMemoryManager::Pool::Application);
@@ -349,8 +349,8 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
// We currently do not support process-specific system resource
UNIMPLEMENTED_IF(system_resource_size != 0);
- KScopedResourceReservation memory_reservation(resource_limit, LimitableResource::PhysicalMemory,
- code_size + system_resource_size);
+ KScopedResourceReservation memory_reservation(
+ resource_limit, LimitableResource::PhysicalMemoryMax, code_size + system_resource_size);
if (!memory_reservation.Succeeded()) {
LOG_ERROR(Kernel, "Could not reserve process memory requirements of size {:X} bytes",
code_size + system_resource_size);
@@ -358,8 +358,8 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
}
// Initialize proces address space
if (const Result result{page_table.InitializeForProcess(
- metadata.GetAddressSpaceType(), false, 0x8000000, code_size,
- &kernel.GetApplicationMemoryBlockManager(), KMemoryManager::Pool::Application)};
+ metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application,
+ 0x8000000, code_size, &kernel.GetSystemSystemResource(), resource_limit)};
result.IsError()) {
R_RETURN(result);
}
@@ -406,8 +406,8 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
void KProcess::Run(s32 main_thread_priority, u64 stack_size) {
AllocateMainThreadStack(stack_size);
- resource_limit->Reserve(LimitableResource::Threads, 1);
- resource_limit->Reserve(LimitableResource::PhysicalMemory, main_thread_stack_size);
+ resource_limit->Reserve(LimitableResource::ThreadCountMax, 1);
+ resource_limit->Reserve(LimitableResource::PhysicalMemoryMax, main_thread_stack_size);
const std::size_t heap_capacity{memory_usage_capacity - (main_thread_stack_size + image_size)};
ASSERT(!page_table.SetMaxHeapSize(heap_capacity).IsError());
@@ -442,7 +442,7 @@ void KProcess::PrepareForTermination() {
plr_address = 0;
if (resource_limit) {
- resource_limit->Release(LimitableResource::PhysicalMemory,
+ resource_limit->Release(LimitableResource::PhysicalMemoryMax,
main_thread_stack_size + image_size);
}
diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp
index 010dcf99e..b9d22b414 100644
--- a/src/core/hle/kernel/k_resource_limit.cpp
+++ b/src/core/hle/kernel/k_resource_limit.cpp
@@ -159,12 +159,13 @@ KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical
// TODO(bunnei): These values are the system defaults, the limits for service processes are
// lower. These should use the correct limit values.
- ASSERT(resource_limit->SetLimitValue(LimitableResource::PhysicalMemory, physical_memory_size)
+ ASSERT(resource_limit->SetLimitValue(LimitableResource::PhysicalMemoryMax, physical_memory_size)
.IsSuccess());
- ASSERT(resource_limit->SetLimitValue(LimitableResource::Threads, 800).IsSuccess());
- ASSERT(resource_limit->SetLimitValue(LimitableResource::Events, 900).IsSuccess());
- ASSERT(resource_limit->SetLimitValue(LimitableResource::TransferMemory, 200).IsSuccess());
- ASSERT(resource_limit->SetLimitValue(LimitableResource::Sessions, 1133).IsSuccess());
+ ASSERT(resource_limit->SetLimitValue(LimitableResource::ThreadCountMax, 800).IsSuccess());
+ ASSERT(resource_limit->SetLimitValue(LimitableResource::EventCountMax, 900).IsSuccess());
+ ASSERT(
+ resource_limit->SetLimitValue(LimitableResource::TransferMemoryCountMax, 200).IsSuccess());
+ ASSERT(resource_limit->SetLimitValue(LimitableResource::SessionCountMax, 1133).IsSuccess());
return resource_limit;
}
diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h
index 65c98c979..2573d1b7c 100644
--- a/src/core/hle/kernel/k_resource_limit.h
+++ b/src/core/hle/kernel/k_resource_limit.h
@@ -16,15 +16,8 @@ class CoreTiming;
namespace Kernel {
class KernelCore;
-enum class LimitableResource : u32 {
- PhysicalMemory = 0,
- Threads = 1,
- Events = 2,
- TransferMemory = 3,
- Sessions = 4,
-
- Count,
-};
+
+using LimitableResource = Svc::LimitableResource;
constexpr bool IsValidResourceType(LimitableResource type) {
return type < LimitableResource::Count;
diff --git a/src/core/hle/kernel/k_scheduler.cpp b/src/core/hle/kernel/k_scheduler.cpp
index c34ce7a17..d6676904b 100644
--- a/src/core/hle/kernel/k_scheduler.cpp
+++ b/src/core/hle/kernel/k_scheduler.cpp
@@ -81,8 +81,8 @@ void KScheduler::RescheduleCurrentHLEThread(KernelCore& kernel) {
// HACK: we cannot schedule from this thread, it is not a core thread
ASSERT(GetCurrentThread(kernel).GetDisableDispatchCount() == 1);
- // Special case to ensure dummy threads that are waiting block
- GetCurrentThread(kernel).IfDummyThreadTryWait();
+ // Ensure dummy threads that are waiting block.
+ GetCurrentThread(kernel).DummyThreadBeginWait();
ASSERT(GetCurrentThread(kernel).GetState() != ThreadState::Waiting);
GetCurrentThread(kernel).EnableDispatch();
@@ -314,6 +314,16 @@ u64 KScheduler::UpdateHighestPriorityThreadsImpl(KernelCore& kernel) {
idle_cores &= ~(1ULL << core_id);
}
+ // HACK: any waiting dummy threads can wake up now.
+ kernel.GlobalSchedulerContext().WakeupWaitingDummyThreads();
+
+ // HACK: if we are a dummy thread, and we need to go sleep, indicate
+ // that for when the lock is released.
+ KThread* const cur_thread = GetCurrentThreadPointer(kernel);
+ if (cur_thread->IsDummyThread() && cur_thread->GetState() != ThreadState::Runnable) {
+ cur_thread->RequestDummyThreadWait();
+ }
+
return cores_needing_scheduling;
}
@@ -374,7 +384,8 @@ void KScheduler::SwitchThread(KThread* next_thread) {
void KScheduler::ScheduleImpl() {
// First, clear the needs scheduling bool.
- m_state.needs_scheduling.store(false, std::memory_order_seq_cst);
+ m_state.needs_scheduling.store(false, std::memory_order_relaxed);
+ std::atomic_thread_fence(std::memory_order_seq_cst);
// Load the appropriate thread pointers for scheduling.
KThread* const cur_thread{GetCurrentThreadPointer(kernel)};
@@ -390,7 +401,8 @@ void KScheduler::ScheduleImpl() {
// If there aren't, we want to check if the highest priority thread is the same as the current
// thread.
if (highest_priority_thread == cur_thread) {
- // If they're the same, then we can just return.
+ // If they're the same, then we can just issue a memory barrier and return.
+ std::atomic_thread_fence(std::memory_order_seq_cst);
return;
}
@@ -466,7 +478,8 @@ void KScheduler::ScheduleImplFiber() {
// We failed to successfully do the context switch, and need to retry.
// Clear needs_scheduling.
- m_state.needs_scheduling.store(false, std::memory_order_seq_cst);
+ m_state.needs_scheduling.store(false, std::memory_order_relaxed);
+ std::atomic_thread_fence(std::memory_order_seq_cst);
// Refresh the highest priority thread.
highest_priority_thread = m_state.highest_priority_thread;
@@ -531,11 +544,23 @@ void KScheduler::OnThreadStateChanged(KernelCore& kernel, KThread* thread, Threa
GetPriorityQueue(kernel).Remove(thread);
IncrementScheduledCount(thread);
SetSchedulerUpdateNeeded(kernel);
+
+ if (thread->IsDummyThread()) {
+ // HACK: if this is a dummy thread, it should no longer wake up when the
+ // scheduler lock is released.
+ kernel.GlobalSchedulerContext().UnregisterDummyThreadForWakeup(thread);
+ }
} else if (cur_state == ThreadState::Runnable) {
// If we're now runnable, then we weren't previously, and we should add.
GetPriorityQueue(kernel).PushBack(thread);
IncrementScheduledCount(thread);
SetSchedulerUpdateNeeded(kernel);
+
+ if (thread->IsDummyThread()) {
+ // HACK: if this is a dummy thread, it should wake up when the scheduler
+ // lock is released.
+ kernel.GlobalSchedulerContext().RegisterDummyThreadForWakeup(thread);
+ }
}
}
diff --git a/src/core/hle/kernel/k_scheduler_lock.h b/src/core/hle/kernel/k_scheduler_lock.h
index 73314b45e..129d60472 100644
--- a/src/core/hle/kernel/k_scheduler_lock.h
+++ b/src/core/hle/kernel/k_scheduler_lock.h
@@ -60,6 +60,9 @@ public:
// Release an instance of the lock.
if ((--lock_count) == 0) {
+ // Perform a memory barrier here.
+ std::atomic_thread_fence(std::memory_order_seq_cst);
+
// We're no longer going to hold the lock. Take note of what cores need scheduling.
const u64 cores_needing_scheduling =
SchedulerType::UpdateHighestPriorityThreads(kernel);
diff --git a/src/core/hle/kernel/k_server_port.cpp b/src/core/hle/kernel/k_server_port.cpp
index e968f26ad..16968ba97 100644
--- a/src/core/hle/kernel/k_server_port.cpp
+++ b/src/core/hle/kernel/k_server_port.cpp
@@ -61,12 +61,6 @@ void KServerPort::Destroy() {
// Close our reference to our parent.
parent->Close();
-
- // Release host emulation members.
- session_handler.reset();
-
- // Ensure that the global list tracking server objects does not hold on to a reference.
- kernel.UnregisterServerObject(this);
}
bool KServerPort::IsSignaled() const {
diff --git a/src/core/hle/kernel/k_server_port.h b/src/core/hle/kernel/k_server_port.h
index fd4f4bd20..5fc7ee683 100644
--- a/src/core/hle/kernel/k_server_port.h
+++ b/src/core/hle/kernel/k_server_port.h
@@ -27,24 +27,6 @@ public:
void Initialize(KPort* parent_port_, std::string&& name_);
- /// Whether or not this server port has an HLE handler available.
- bool HasSessionRequestHandler() const {
- return !session_handler.expired();
- }
-
- /// Gets the HLE handler for this port.
- SessionRequestHandlerWeakPtr GetSessionRequestHandler() const {
- return session_handler;
- }
-
- /**
- * Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
- * will inherit a reference to this handler.
- */
- void SetSessionHandler(SessionRequestHandlerWeakPtr&& handler) {
- session_handler = std::move(handler);
- }
-
void EnqueueSession(KServerSession* pending_session);
KServerSession* AcceptSession();
@@ -65,7 +47,6 @@ private:
void CleanupSessions();
SessionList session_list;
- SessionRequestHandlerWeakPtr session_handler;
KPort* parent{};
};
diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp
index faf03fcc8..aa1941f01 100644
--- a/src/core/hle/kernel/k_server_session.cpp
+++ b/src/core/hle/kernel/k_server_session.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <tuple>
@@ -33,12 +33,10 @@ KServerSession::KServerSession(KernelCore& kernel_)
KServerSession::~KServerSession() = default;
-void KServerSession::Initialize(KSession* parent_session_, std::string&& name_,
- std::shared_ptr<SessionRequestManager> manager_) {
+void KServerSession::Initialize(KSession* parent_session_, std::string&& name_) {
// Set member variables.
parent = parent_session_;
name = std::move(name_);
- manager = manager_;
}
void KServerSession::Destroy() {
@@ -47,18 +45,99 @@ void KServerSession::Destroy() {
this->CleanupRequests();
parent->Close();
-
- // Release host emulation members.
- manager.reset();
-
- // Ensure that the global list tracking server objects does not hold on to a reference.
- kernel.UnregisterServerObject(this);
}
void KServerSession::OnClientClosed() {
- if (manager && manager->HasSessionHandler()) {
- manager->SessionHandler().ClientDisconnected(this);
+ KScopedLightLock lk{m_lock};
+
+ // Handle any pending requests.
+ KSessionRequest* prev_request = nullptr;
+ while (true) {
+ // Declare variables for processing the request.
+ KSessionRequest* request = nullptr;
+ KEvent* event = nullptr;
+ KThread* thread = nullptr;
+ bool cur_request = false;
+ bool terminate = false;
+
+ // Get the next request.
+ {
+ KScopedSchedulerLock sl{kernel};
+
+ if (m_current_request != nullptr && m_current_request != prev_request) {
+ // Set the request, open a reference as we process it.
+ request = m_current_request;
+ request->Open();
+ cur_request = true;
+
+ // Get thread and event for the request.
+ thread = request->GetThread();
+ event = request->GetEvent();
+
+ // If the thread is terminating, handle that.
+ if (thread->IsTerminationRequested()) {
+ request->ClearThread();
+ request->ClearEvent();
+ terminate = true;
+ }
+
+ prev_request = request;
+ } else if (!m_request_list.empty()) {
+ // Pop the request from the front of the list.
+ request = std::addressof(m_request_list.front());
+ m_request_list.pop_front();
+
+ // Get thread and event for the request.
+ thread = request->GetThread();
+ event = request->GetEvent();
+ }
+ }
+
+ // If there are no requests, we're done.
+ if (request == nullptr) {
+ break;
+ }
+
+ // All requests must have threads.
+ ASSERT(thread != nullptr);
+
+ // Ensure that we close the request when done.
+ SCOPE_EXIT({ request->Close(); });
+
+ // If we're terminating, close a reference to the thread and event.
+ if (terminate) {
+ thread->Close();
+ if (event != nullptr) {
+ event->Close();
+ }
+ }
+
+ // If we need to, reply.
+ if (event != nullptr && !cur_request) {
+ // There must be no mappings.
+ ASSERT(request->GetSendCount() == 0);
+ ASSERT(request->GetReceiveCount() == 0);
+ ASSERT(request->GetExchangeCount() == 0);
+
+ // // Get the process and page table.
+ // KProcess *client_process = thread->GetOwnerProcess();
+ // auto &client_pt = client_process->GetPageTable();
+
+ // // Reply to the request.
+ // ReplyAsyncError(client_process, request->GetAddress(), request->GetSize(),
+ // ResultSessionClosed);
+
+ // // Unlock the buffer.
+ // // NOTE: Nintendo does not check the result of this.
+ // client_pt.UnlockForIpcUserBuffer(request->GetAddress(), request->GetSize());
+
+ // Signal the event.
+ event->Signal();
+ }
}
+
+ // Notify.
+ this->NotifyAvailable(ResultSessionClosed);
}
bool KServerSession::IsSignaled() const {
@@ -73,24 +152,6 @@ bool KServerSession::IsSignaled() const {
return !m_request_list.empty() && m_current_request == nullptr;
}
-Result KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory) {
- u32* cmd_buf{reinterpret_cast<u32*>(memory.GetPointer(thread->GetTLSAddress()))};
- auto context = std::make_shared<HLERequestContext>(kernel, memory, this, thread);
-
- context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
-
- return manager->QueueSyncRequest(parent, std::move(context));
-}
-
-Result KServerSession::CompleteSyncRequest(HLERequestContext& context) {
- Result result = manager->CompleteSyncRequest(this, context);
-
- // The calling thread is waiting for this request to complete, so wake it up.
- context.GetThread().EndWait(result);
-
- return result;
-}
-
Result KServerSession::OnRequest(KSessionRequest* request) {
// Create the wait queue.
ThreadQueueImplForKServerSessionRequest wait_queue{kernel};
@@ -105,24 +166,16 @@ Result KServerSession::OnRequest(KSessionRequest* request) {
// Check that we're not terminating.
R_UNLESS(!GetCurrentThread(kernel).IsTerminationRequested(), ResultTerminationRequested);
- if (manager) {
- // HLE request.
- auto& memory{kernel.System().Memory()};
- this->QueueSyncRequest(GetCurrentThreadPointer(kernel), memory);
- } else {
- // Non-HLE request.
-
- // Get whether we're empty.
- const bool was_empty = m_request_list.empty();
+ // Get whether we're empty.
+ const bool was_empty = m_request_list.empty();
- // Add the request to the list.
- request->Open();
- m_request_list.push_back(*request);
+ // Add the request to the list.
+ request->Open();
+ m_request_list.push_back(*request);
- // If we were empty, signal.
- if (was_empty) {
- this->NotifyAvailable();
- }
+ // If we were empty, signal.
+ if (was_empty) {
+ this->NotifyAvailable();
}
// If we have a request event, this is asynchronous, and we don't need to wait.
@@ -136,7 +189,7 @@ Result KServerSession::OnRequest(KSessionRequest* request) {
return GetCurrentThread(kernel).GetWaitResult();
}
-Result KServerSession::SendReply() {
+Result KServerSession::SendReply(bool is_hle) {
// Lock the session.
KScopedLightLock lk{m_lock};
@@ -171,13 +224,18 @@ Result KServerSession::SendReply() {
Result result = ResultSuccess;
if (!closed) {
// If we're not closed, send the reply.
- Core::Memory::Memory& memory{kernel.System().Memory()};
- KThread* server_thread{GetCurrentThreadPointer(kernel)};
- UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
+ if (is_hle) {
+ // HLE servers write directly to a pointer to the thread command buffer. Therefore
+ // the reply has already been written in this case.
+ } else {
+ Core::Memory::Memory& memory{kernel.System().Memory()};
+ KThread* server_thread{GetCurrentThreadPointer(kernel)};
+ UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
- auto* src_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
- auto* dst_msg_buffer = memory.GetPointer(client_message);
- std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
+ auto* src_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
+ auto* dst_msg_buffer = memory.GetPointer(client_message);
+ std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
+ }
} else {
result = ResultSessionClosed;
}
@@ -223,7 +281,8 @@ Result KServerSession::SendReply() {
return result;
}
-Result KServerSession::ReceiveRequest() {
+Result KServerSession::ReceiveRequest(std::shared_ptr<HLERequestContext>* out_context,
+ std::weak_ptr<SessionRequestManager> manager) {
// Lock the session.
KScopedLightLock lk{m_lock};
@@ -267,12 +326,22 @@ Result KServerSession::ReceiveRequest() {
// Receive the message.
Core::Memory::Memory& memory{kernel.System().Memory()};
- KThread* server_thread{GetCurrentThreadPointer(kernel)};
- UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
+ if (out_context != nullptr) {
+ // HLE request.
+ u32* cmd_buf{reinterpret_cast<u32*>(memory.GetPointer(client_message))};
+ *out_context = std::make_shared<HLERequestContext>(kernel, memory, this, client_thread);
+ (*out_context)->SetSessionRequestManager(manager);
+ (*out_context)
+ ->PopulateFromIncomingCommandBuffer(client_thread->GetOwnerProcess()->GetHandleTable(),
+ cmd_buf);
+ } else {
+ KThread* server_thread{GetCurrentThreadPointer(kernel)};
+ UNIMPLEMENTED_IF(server_thread->GetOwnerProcess() != client_thread->GetOwnerProcess());
- auto* src_msg_buffer = memory.GetPointer(client_message);
- auto* dst_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
- std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
+ auto* src_msg_buffer = memory.GetPointer(client_message);
+ auto* dst_msg_buffer = memory.GetPointer(server_thread->GetTLSAddress());
+ std::memcpy(dst_msg_buffer, src_msg_buffer, client_buffer_size);
+ }
// We succeeded.
return ResultSuccess;
diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h
index 32135473b..6e189af8b 100644
--- a/src/core/hle/kernel/k_server_session.h
+++ b/src/core/hle/kernel/k_server_session.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -16,21 +16,11 @@
#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/result.h"
-namespace Core::Memory {
-class Memory;
-}
-
-namespace Core::Timing {
-class CoreTiming;
-struct EventType;
-} // namespace Core::Timing
-
namespace Kernel {
class HLERequestContext;
class KernelCore;
class KSession;
-class SessionRequestHandler;
class SessionRequestManager;
class KThread;
@@ -46,8 +36,7 @@ public:
void Destroy() override;
- void Initialize(KSession* parent_session_, std::string&& name_,
- std::shared_ptr<SessionRequestManager> manager_);
+ void Initialize(KSession* parent_session_, std::string&& name_);
KSession* GetParent() {
return parent;
@@ -60,38 +49,26 @@ public:
bool IsSignaled() const override;
void OnClientClosed();
- /// Gets the session request manager, which forwards requests to the underlying service
- std::shared_ptr<SessionRequestManager>& GetSessionRequestManager() {
- return manager;
- }
-
/// TODO: flesh these out to match the real kernel
Result OnRequest(KSessionRequest* request);
- Result SendReply();
- Result ReceiveRequest();
+ Result SendReply(bool is_hle = false);
+ Result ReceiveRequest(std::shared_ptr<HLERequestContext>* out_context = nullptr,
+ std::weak_ptr<SessionRequestManager> manager = {});
+
+ Result SendReplyHLE() {
+ return SendReply(true);
+ }
private:
/// Frees up waiting client sessions when this server session is about to die
void CleanupRequests();
- /// Queues a sync request from the emulated application.
- Result QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory);
-
- /// Completes a sync request from the emulated application.
- Result CompleteSyncRequest(HLERequestContext& context);
-
- /// This session's HLE request handlers; if nullptr, this is not an HLE server
- std::shared_ptr<SessionRequestManager> manager;
-
- /// When set to True, converts the session to a domain at the end of the command
- bool convert_to_domain{};
-
/// KSession that owns this KServerSession
KSession* parent{};
/// List of threads which are pending a reply.
boost::intrusive::list<KSessionRequest> m_request_list;
- KSessionRequest* m_current_request;
+ KSessionRequest* m_current_request{};
KLightLock m_lock;
};
diff --git a/src/core/hle/kernel/k_session.cpp b/src/core/hle/kernel/k_session.cpp
index ee05aa282..b6f6fe9d9 100644
--- a/src/core/hle/kernel/k_session.cpp
+++ b/src/core/hle/kernel/k_session.cpp
@@ -13,8 +13,7 @@ KSession::KSession(KernelCore& kernel_)
: KAutoObjectWithSlabHeapAndContainer{kernel_}, server{kernel_}, client{kernel_} {}
KSession::~KSession() = default;
-void KSession::Initialize(KClientPort* port_, const std::string& name_,
- std::shared_ptr<SessionRequestManager> manager_) {
+void KSession::Initialize(KClientPort* port_, const std::string& name_) {
// Increment reference count.
// Because reference count is one on creation, this will result
// in a reference count of two. Thus, when both server and client are closed
@@ -26,7 +25,7 @@ void KSession::Initialize(KClientPort* port_, const std::string& name_,
KAutoObject::Create(std::addressof(client));
// Initialize our sub sessions.
- server.Initialize(this, name_ + ":Server", manager_);
+ server.Initialize(this, name_ + ":Server");
client.Initialize(this, name_ + ":Client");
// Set state and name.
@@ -77,7 +76,7 @@ void KSession::OnClientClosed() {
void KSession::PostDestroy(uintptr_t arg) {
// Release the session count resource the owner process holds.
KProcess* owner = reinterpret_cast<KProcess*>(arg);
- owner->GetResourceLimit()->Release(LimitableResource::Sessions, 1);
+ owner->GetResourceLimit()->Release(LimitableResource::SessionCountMax, 1);
owner->Close();
}
diff --git a/src/core/hle/kernel/k_session.h b/src/core/hle/kernel/k_session.h
index c6ead403b..93e5e6f71 100644
--- a/src/core/hle/kernel/k_session.h
+++ b/src/core/hle/kernel/k_session.h
@@ -21,8 +21,7 @@ public:
explicit KSession(KernelCore& kernel_);
~KSession() override;
- void Initialize(KClientPort* port_, const std::string& name_,
- std::shared_ptr<SessionRequestManager> manager_ = nullptr);
+ void Initialize(KClientPort* port_, const std::string& name_);
void Finalize() override;
diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp
index a039cc591..10cd4c43d 100644
--- a/src/core/hle/kernel/k_shared_memory.cpp
+++ b/src/core/hle/kernel/k_shared_memory.cpp
@@ -14,7 +14,7 @@ namespace Kernel {
KSharedMemory::KSharedMemory(KernelCore& kernel_) : KAutoObjectWithSlabHeapAndContainer{kernel_} {}
KSharedMemory::~KSharedMemory() {
- kernel.GetSystemResourceLimit()->Release(LimitableResource::PhysicalMemory, size);
+ kernel.GetSystemResourceLimit()->Release(LimitableResource::PhysicalMemoryMax, size);
}
Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_,
@@ -35,7 +35,7 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o
KResourceLimit* reslimit = kernel.GetSystemResourceLimit();
// Reserve memory for ourselves.
- KScopedResourceReservation memory_reservation(reslimit, LimitableResource::PhysicalMemory,
+ KScopedResourceReservation memory_reservation(reslimit, LimitableResource::PhysicalMemoryMax,
size_);
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
@@ -57,7 +57,7 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o
void KSharedMemory::Finalize() {
// Release the memory reservation.
- resource_limit->Release(LimitableResource::PhysicalMemory, size);
+ resource_limit->Release(LimitableResource::PhysicalMemoryMax, size);
resource_limit->Close();
// Perform inherited finalization.
diff --git a/src/core/hle/kernel/k_system_resource.cpp b/src/core/hle/kernel/k_system_resource.cpp
new file mode 100644
index 000000000..4cc377a6c
--- /dev/null
+++ b/src/core/hle/kernel/k_system_resource.cpp
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/kernel/k_system_resource.h"
+
+namespace Kernel {
+
+Result KSecureSystemResource::Initialize([[maybe_unused]] size_t size,
+ [[maybe_unused]] KResourceLimit* resource_limit,
+ [[maybe_unused]] KMemoryManager::Pool pool) {
+ // Unimplemented
+ UNREACHABLE();
+}
+
+void KSecureSystemResource::Finalize() {
+ // Unimplemented
+ UNREACHABLE();
+}
+
+size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(
+ [[maybe_unused]] size_t size, [[maybe_unused]] KMemoryManager::Pool pool) {
+ // Unimplemented
+ UNREACHABLE();
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_system_resource.h b/src/core/hle/kernel/k_system_resource.h
new file mode 100644
index 000000000..9a991f725
--- /dev/null
+++ b/src/core/hle/kernel/k_system_resource.h
@@ -0,0 +1,137 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "core/hle/kernel/k_auto_object.h"
+#include "core/hle/kernel/k_dynamic_resource_manager.h"
+#include "core/hle/kernel/k_memory_manager.h"
+#include "core/hle/kernel/k_page_table_manager.h"
+#include "core/hle/kernel/k_resource_limit.h"
+#include "core/hle/kernel/slab_helpers.h"
+
+namespace Kernel {
+
+// NOTE: Nintendo's implementation does not have the "is_secure_resource" field, and instead uses
+// virtual IsSecureResource().
+
+class KSystemResource : public KAutoObject {
+ KERNEL_AUTOOBJECT_TRAITS(KSystemResource, KAutoObject);
+
+public:
+ explicit KSystemResource(KernelCore& kernel_) : KAutoObject(kernel_) {}
+
+protected:
+ void SetSecureResource() {
+ m_is_secure_resource = true;
+ }
+
+public:
+ virtual void Destroy() override {
+ UNREACHABLE_MSG("KSystemResource::Destroy() was called");
+ }
+
+ bool IsSecureResource() const {
+ return m_is_secure_resource;
+ }
+
+ void SetManagers(KMemoryBlockSlabManager& mb, KBlockInfoManager& bi, KPageTableManager& pt) {
+ ASSERT(m_p_memory_block_slab_manager == nullptr);
+ ASSERT(m_p_block_info_manager == nullptr);
+ ASSERT(m_p_page_table_manager == nullptr);
+
+ m_p_memory_block_slab_manager = std::addressof(mb);
+ m_p_block_info_manager = std::addressof(bi);
+ m_p_page_table_manager = std::addressof(pt);
+ }
+
+ const KMemoryBlockSlabManager& GetMemoryBlockSlabManager() const {
+ return *m_p_memory_block_slab_manager;
+ }
+ const KBlockInfoManager& GetBlockInfoManager() const {
+ return *m_p_block_info_manager;
+ }
+ const KPageTableManager& GetPageTableManager() const {
+ return *m_p_page_table_manager;
+ }
+
+ KMemoryBlockSlabManager& GetMemoryBlockSlabManager() {
+ return *m_p_memory_block_slab_manager;
+ }
+ KBlockInfoManager& GetBlockInfoManager() {
+ return *m_p_block_info_manager;
+ }
+ KPageTableManager& GetPageTableManager() {
+ return *m_p_page_table_manager;
+ }
+
+ KMemoryBlockSlabManager* GetMemoryBlockSlabManagerPointer() {
+ return m_p_memory_block_slab_manager;
+ }
+ KBlockInfoManager* GetBlockInfoManagerPointer() {
+ return m_p_block_info_manager;
+ }
+ KPageTableManager* GetPageTableManagerPointer() {
+ return m_p_page_table_manager;
+ }
+
+private:
+ KMemoryBlockSlabManager* m_p_memory_block_slab_manager{};
+ KBlockInfoManager* m_p_block_info_manager{};
+ KPageTableManager* m_p_page_table_manager{};
+ bool m_is_secure_resource{false};
+};
+
+class KSecureSystemResource final
+ : public KAutoObjectWithSlabHeap<KSecureSystemResource, KSystemResource> {
+public:
+ explicit KSecureSystemResource(KernelCore& kernel_)
+ : KAutoObjectWithSlabHeap<KSecureSystemResource, KSystemResource>(kernel_) {
+ // Mark ourselves as being a secure resource.
+ this->SetSecureResource();
+ }
+
+ Result Initialize(size_t size, KResourceLimit* resource_limit, KMemoryManager::Pool pool);
+ void Finalize();
+
+ bool IsInitialized() const {
+ return m_is_initialized;
+ }
+ static void PostDestroy([[maybe_unused]] uintptr_t arg) {}
+
+ size_t CalculateRequiredSecureMemorySize() const {
+ return CalculateRequiredSecureMemorySize(m_resource_size, m_resource_pool);
+ }
+
+ size_t GetSize() const {
+ return m_resource_size;
+ }
+ size_t GetUsedSize() const {
+ return m_dynamic_page_manager.GetUsed() * PageSize;
+ }
+
+ const KDynamicPageManager& GetDynamicPageManager() const {
+ return m_dynamic_page_manager;
+ }
+
+public:
+ static size_t CalculateRequiredSecureMemorySize(size_t size, KMemoryManager::Pool pool);
+
+private:
+ bool m_is_initialized{};
+ KMemoryManager::Pool m_resource_pool{};
+ KDynamicPageManager m_dynamic_page_manager;
+ KMemoryBlockSlabManager m_memory_block_slab_manager;
+ KBlockInfoManager m_block_info_manager;
+ KPageTableManager m_page_table_manager;
+ KMemoryBlockSlabHeap m_memory_block_heap;
+ KBlockInfoSlabHeap m_block_info_heap;
+ KPageTableSlabHeap m_page_table_heap;
+ KResourceLimit* m_resource_limit{};
+ VAddr m_resource_address{};
+ size_t m_resource_size{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
index b7bfcdce3..21207fe99 100644
--- a/src/core/hle/kernel/k_thread.cpp
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -148,7 +148,9 @@ Result KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_stack
physical_affinity_mask.SetAffinity(phys_core, true);
// Set the thread state.
- thread_state = (type == ThreadType::Main) ? ThreadState::Runnable : ThreadState::Initialized;
+ thread_state = (type == ThreadType::Main || type == ThreadType::Dummy)
+ ? ThreadState::Runnable
+ : ThreadState::Initialized;
// Set TLS address.
tls_address = 0;
@@ -261,9 +263,9 @@ Result KThread::InitializeThread(KThread* thread, KThreadFunction func, uintptr_
R_SUCCEED();
}
-Result KThread::InitializeDummyThread(KThread* thread) {
+Result KThread::InitializeDummyThread(KThread* thread, KProcess* owner) {
// Initialize the thread.
- R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, {}, ThreadType::Dummy));
+ R_TRY(thread->Initialize({}, {}, {}, DummyThreadPriority, 3, owner, ThreadType::Dummy));
// Initialize emulation parameters.
thread->stack_parameters.disable_count = 0;
@@ -301,7 +303,7 @@ void KThread::PostDestroy(uintptr_t arg) {
const bool resource_limit_release_hint = (arg & 1);
const s64 hint_value = (resource_limit_release_hint ? 0 : 1);
if (owner != nullptr) {
- owner->GetResourceLimit()->Release(LimitableResource::Threads, 1, hint_value);
+ owner->GetResourceLimit()->Release(LimitableResource::ThreadCountMax, 1, hint_value);
owner->Close();
}
}
@@ -1052,7 +1054,7 @@ void KThread::Exit() {
// Release the thread resource hint, running thread count from parent.
if (parent != nullptr) {
- parent->GetResourceLimit()->Release(Kernel::LimitableResource::Threads, 0, 1);
+ parent->GetResourceLimit()->Release(Kernel::LimitableResource::ThreadCountMax, 0, 1);
resource_limit_release_hint = true;
parent->DecrementRunningThreadCount();
}
@@ -1174,30 +1176,31 @@ Result KThread::Sleep(s64 timeout) {
R_SUCCEED();
}
-void KThread::IfDummyThreadTryWait() {
- if (!IsDummyThread()) {
- return;
- }
+void KThread::RequestDummyThreadWait() {
+ ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel));
+ ASSERT(this->IsDummyThread());
+
+ // We will block when the scheduler lock is released.
+ dummy_thread_runnable.store(false);
+}
- if (GetState() != ThreadState::Waiting) {
+void KThread::DummyThreadBeginWait() {
+ if (!this->IsDummyThread() || kernel.IsPhantomModeForSingleCore()) {
+ // Occurs in single core mode.
return;
}
- ASSERT(!kernel.IsPhantomModeForSingleCore());
-
- // Block until we are no longer waiting.
- std::unique_lock lk(dummy_wait_lock);
- dummy_wait_cv.wait(
- lk, [&] { return GetState() != ThreadState::Waiting || kernel.IsShuttingDown(); });
+ // Block until runnable is no longer false.
+ dummy_thread_runnable.wait(false);
}
-void KThread::IfDummyThreadEndWait() {
- if (!IsDummyThread()) {
- return;
- }
+void KThread::DummyThreadEndWait() {
+ ASSERT(KScheduler::IsSchedulerLockedByCurrentThread(kernel));
+ ASSERT(this->IsDummyThread());
// Wake up the waiting thread.
- dummy_wait_cv.notify_one();
+ dummy_thread_runnable.store(true);
+ dummy_thread_runnable.notify_one();
}
void KThread::BeginWait(KThreadQueue* queue) {
@@ -1231,9 +1234,6 @@ void KThread::EndWait(Result wait_result_) {
}
wait_queue->EndWait(this, wait_result_);
-
- // Special case for dummy threads to wakeup if necessary.
- IfDummyThreadEndWait();
}
}
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index e2a27d603..f38c92bff 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -415,7 +415,7 @@ public:
static void PostDestroy(uintptr_t arg);
- [[nodiscard]] static Result InitializeDummyThread(KThread* thread);
+ [[nodiscard]] static Result InitializeDummyThread(KThread* thread, KProcess* owner);
[[nodiscard]] static Result InitializeMainThread(Core::System& system, KThread* thread,
s32 virt_core);
@@ -643,8 +643,9 @@ public:
// therefore will not block on guest kernel synchronization primitives. These methods handle
// blocking as needed.
- void IfDummyThreadTryWait();
- void IfDummyThreadEndWait();
+ void RequestDummyThreadWait();
+ void DummyThreadBeginWait();
+ void DummyThreadEndWait();
[[nodiscard]] uintptr_t GetArgument() const {
return argument;
@@ -777,8 +778,7 @@ private:
bool is_single_core{};
ThreadType thread_type{};
StepState step_state{};
- std::mutex dummy_wait_lock;
- std::condition_variable dummy_wait_cv;
+ std::atomic<bool> dummy_thread_runnable{true};
// For debugging
std::vector<KSynchronizationObject*> wait_objects_for_debugging;
diff --git a/src/core/hle/kernel/k_transfer_memory.cpp b/src/core/hle/kernel/k_transfer_memory.cpp
index b0320eb73..9f34c2d46 100644
--- a/src/core/hle/kernel/k_transfer_memory.cpp
+++ b/src/core/hle/kernel/k_transfer_memory.cpp
@@ -37,7 +37,7 @@ void KTransferMemory::Finalize() {
void KTransferMemory::PostDestroy(uintptr_t arg) {
KProcess* owner = reinterpret_cast<KProcess*>(arg);
- owner->GetResourceLimit()->Release(LimitableResource::TransferMemory, 1);
+ owner->GetResourceLimit()->Release(LimitableResource::TransferMemoryCountMax, 1);
owner->Close();
}
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index fdc774e30..b77723503 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -28,10 +28,12 @@
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_memory_manager.h"
+#include "core/hle/kernel/k_page_buffer.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_shared_memory.h"
+#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_worker_task_manager.h"
#include "core/hle/kernel/kernel.h"
@@ -47,6 +49,11 @@ MICROPROFILE_DEFINE(Kernel_SVC, "Kernel", "SVC", MP_RGB(70, 200, 70));
namespace Kernel {
struct KernelCore::Impl {
+ static constexpr size_t ApplicationMemoryBlockSlabHeapSize = 20000;
+ static constexpr size_t SystemMemoryBlockSlabHeapSize = 10000;
+ static constexpr size_t BlockInfoSlabHeapSize = 4000;
+ static constexpr size_t ReservedDynamicPageCount = 64;
+
explicit Impl(Core::System& system_, KernelCore& kernel_)
: time_manager{system_}, service_threads_manager{1, "ServiceThreadsManager"},
service_thread_barrier{2}, system{system_} {}
@@ -60,7 +67,6 @@ struct KernelCore::Impl {
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
global_handle_table = std::make_unique<Kernel::KHandleTable>(kernel);
global_handle_table->Initialize(KHandleTable::MaxTableSize);
- default_service_thread = CreateServiceThread(kernel, "DefaultServiceThread");
is_phantom_mode_for_singlecore = false;
@@ -72,7 +78,6 @@ struct KernelCore::Impl {
// Initialize kernel memory and resources.
InitializeSystemResourceLimit(kernel, system.CoreTiming());
InitializeMemoryLayout();
- Init::InitializeKPageBufferSlabHeap(system);
InitializeShutdownThreads();
InitializePhysicalCores();
InitializePreemption(kernel);
@@ -82,10 +87,13 @@ struct KernelCore::Impl {
const auto& pt_heap_region = memory_layout->GetPageTableHeapRegion();
ASSERT(pt_heap_region.GetEndAddress() != 0);
- InitializeResourceManagers(pt_heap_region.GetAddress(), pt_heap_region.GetSize());
+ InitializeResourceManagers(kernel, pt_heap_region.GetAddress(),
+ pt_heap_region.GetSize());
}
- RegisterHostThread();
+ RegisterHostThread(nullptr);
+
+ default_service_thread = &CreateServiceThread(kernel, "DefaultServiceThread");
}
void InitializeCores() {
@@ -184,17 +192,6 @@ struct KernelCore::Impl {
}
void CloseServices() {
- // Close all open server sessions and ports.
- std::unordered_set<KAutoObject*> server_objects_;
- {
- std::scoped_lock lk(server_objects_lock);
- server_objects_ = server_objects;
- server_objects.clear();
- }
- for (auto* server_object : server_objects_) {
- server_object->Close();
- }
-
// Ensures all service threads gracefully shutdown.
ClearServiceThreads();
}
@@ -232,18 +229,22 @@ struct KernelCore::Impl {
const auto kernel_size{sizes.second};
// If setting the default system values fails, then something seriously wrong has occurred.
- ASSERT(system_resource_limit->SetLimitValue(LimitableResource::PhysicalMemory, total_size)
+ ASSERT(
+ system_resource_limit->SetLimitValue(LimitableResource::PhysicalMemoryMax, total_size)
+ .IsSuccess());
+ ASSERT(system_resource_limit->SetLimitValue(LimitableResource::ThreadCountMax, 800)
+ .IsSuccess());
+ ASSERT(system_resource_limit->SetLimitValue(LimitableResource::EventCountMax, 900)
+ .IsSuccess());
+ ASSERT(system_resource_limit->SetLimitValue(LimitableResource::TransferMemoryCountMax, 200)
.IsSuccess());
- ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Threads, 800).IsSuccess());
- ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Events, 900).IsSuccess());
- ASSERT(system_resource_limit->SetLimitValue(LimitableResource::TransferMemory, 200)
+ ASSERT(system_resource_limit->SetLimitValue(LimitableResource::SessionCountMax, 1133)
.IsSuccess());
- ASSERT(system_resource_limit->SetLimitValue(LimitableResource::Sessions, 1133).IsSuccess());
- system_resource_limit->Reserve(LimitableResource::PhysicalMemory, kernel_size);
+ system_resource_limit->Reserve(LimitableResource::PhysicalMemoryMax, kernel_size);
// Reserve secure applet memory, introduced in firmware 5.0.0
constexpr u64 secure_applet_memory_size{4_MiB};
- ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemory,
+ ASSERT(system_resource_limit->Reserve(LimitableResource::PhysicalMemoryMax,
secure_applet_memory_size));
}
@@ -263,16 +264,82 @@ struct KernelCore::Impl {
system.CoreTiming().ScheduleLoopingEvent(time_interval, time_interval, preemption_event);
}
- void InitializeResourceManagers(VAddr address, size_t size) {
- dynamic_page_manager = std::make_unique<KDynamicPageManager>();
- memory_block_heap = std::make_unique<KMemoryBlockSlabHeap>();
+ void InitializeResourceManagers(KernelCore& kernel, VAddr address, size_t size) {
+ // Ensure that the buffer is suitable for our use.
+ ASSERT(Common::IsAligned(address, PageSize));
+ ASSERT(Common::IsAligned(size, PageSize));
+
+ // Ensure that we have space for our reference counts.
+ const size_t rc_size =
+ Common::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(size), PageSize);
+ ASSERT(rc_size < size);
+ size -= rc_size;
+
+ // Initialize the resource managers' shared page manager.
+ resource_manager_page_manager = std::make_unique<KDynamicPageManager>();
+ resource_manager_page_manager->Initialize(
+ address, size, std::max<size_t>(PageSize, KPageBufferSlabHeap::BufferSize));
+
+ // Initialize the KPageBuffer slab heap.
+ page_buffer_slab_heap.Initialize(system);
+
+ // Initialize the fixed-size slabheaps.
+ app_memory_block_heap = std::make_unique<KMemoryBlockSlabHeap>();
+ sys_memory_block_heap = std::make_unique<KMemoryBlockSlabHeap>();
+ block_info_heap = std::make_unique<KBlockInfoSlabHeap>();
+ app_memory_block_heap->Initialize(resource_manager_page_manager.get(),
+ ApplicationMemoryBlockSlabHeapSize);
+ sys_memory_block_heap->Initialize(resource_manager_page_manager.get(),
+ SystemMemoryBlockSlabHeapSize);
+ block_info_heap->Initialize(resource_manager_page_manager.get(), BlockInfoSlabHeapSize);
+
+ // Reserve all but a fixed number of remaining pages for the page table heap.
+ const size_t num_pt_pages = resource_manager_page_manager->GetCount() -
+ resource_manager_page_manager->GetUsed() -
+ ReservedDynamicPageCount;
+ page_table_heap = std::make_unique<KPageTableSlabHeap>();
+
+ // TODO(bunnei): Pass in address once we support kernel virtual memory allocations.
+ page_table_heap->Initialize(
+ resource_manager_page_manager.get(), num_pt_pages,
+ /*GetPointer<KPageTableManager::RefCount>(address + size)*/ nullptr);
+
+ // Setup the slab managers.
+ KDynamicPageManager* const app_dynamic_page_manager = nullptr;
+ KDynamicPageManager* const sys_dynamic_page_manager =
+ /*KTargetSystem::IsDynamicResourceLimitsEnabled()*/ true
+ ? resource_manager_page_manager.get()
+ : nullptr;
app_memory_block_manager = std::make_unique<KMemoryBlockSlabManager>();
-
- dynamic_page_manager->Initialize(address, size);
- static constexpr size_t ApplicationMemoryBlockSlabHeapSize = 20000;
- memory_block_heap->Initialize(dynamic_page_manager.get(),
- ApplicationMemoryBlockSlabHeapSize);
- app_memory_block_manager->Initialize(nullptr, memory_block_heap.get());
+ sys_memory_block_manager = std::make_unique<KMemoryBlockSlabManager>();
+ app_block_info_manager = std::make_unique<KBlockInfoManager>();
+ sys_block_info_manager = std::make_unique<KBlockInfoManager>();
+ app_page_table_manager = std::make_unique<KPageTableManager>();
+ sys_page_table_manager = std::make_unique<KPageTableManager>();
+
+ app_memory_block_manager->Initialize(app_dynamic_page_manager, app_memory_block_heap.get());
+ sys_memory_block_manager->Initialize(sys_dynamic_page_manager, sys_memory_block_heap.get());
+
+ app_block_info_manager->Initialize(app_dynamic_page_manager, block_info_heap.get());
+ sys_block_info_manager->Initialize(sys_dynamic_page_manager, block_info_heap.get());
+
+ app_page_table_manager->Initialize(app_dynamic_page_manager, page_table_heap.get());
+ sys_page_table_manager->Initialize(sys_dynamic_page_manager, page_table_heap.get());
+
+ // Check that we have the correct number of dynamic pages available.
+ ASSERT(resource_manager_page_manager->GetCount() -
+ resource_manager_page_manager->GetUsed() ==
+ ReservedDynamicPageCount);
+
+ // Create the system page table managers.
+ app_system_resource = std::make_unique<KSystemResource>(kernel);
+ sys_system_resource = std::make_unique<KSystemResource>(kernel);
+
+ // Set the managers for the system resources.
+ app_system_resource->SetManagers(*app_memory_block_manager, *app_block_info_manager,
+ *app_page_table_manager);
+ sys_system_resource->SetManagers(*sys_memory_block_manager, *sys_block_info_manager,
+ *sys_page_table_manager);
}
void InitializeShutdownThreads() {
@@ -310,15 +377,18 @@ struct KernelCore::Impl {
}
// Gets the dummy KThread for the caller, allocating a new one if this is the first time
- KThread* GetHostDummyThread() {
+ KThread* GetHostDummyThread(KThread* existing_thread) {
auto initialize = [this](KThread* thread) {
- ASSERT(KThread::InitializeDummyThread(thread).IsSuccess());
+ ASSERT(KThread::InitializeDummyThread(thread, nullptr).IsSuccess());
thread->SetName(fmt::format("DummyThread:{}", GetHostThreadId()));
return thread;
};
- thread_local auto raw_thread = KThread(system.Kernel());
- thread_local auto thread = initialize(&raw_thread);
+ thread_local KThread raw_thread{system.Kernel()};
+ thread_local KThread* thread = nullptr;
+ if (thread == nullptr) {
+ thread = (existing_thread == nullptr) ? initialize(&raw_thread) : existing_thread;
+ }
return thread;
}
@@ -333,9 +403,9 @@ struct KernelCore::Impl {
}
/// Registers a new host thread by allocating a host thread ID for it
- void RegisterHostThread() {
+ void RegisterHostThread(KThread* existing_thread) {
[[maybe_unused]] const auto this_id = GetHostThreadId();
- [[maybe_unused]] const auto dummy_thread = GetHostDummyThread();
+ [[maybe_unused]] const auto dummy_thread = GetHostDummyThread(existing_thread);
}
[[nodiscard]] u32 GetCurrentHostThreadID() {
@@ -346,6 +416,8 @@ struct KernelCore::Impl {
return this_id;
}
+ static inline thread_local bool is_phantom_mode_for_singlecore{false};
+
bool IsPhantomModeForSingleCore() const {
return is_phantom_mode_for_singlecore;
}
@@ -364,7 +436,7 @@ struct KernelCore::Impl {
KThread* GetCurrentEmuThread() {
const auto thread_id = GetCurrentHostThreadID();
if (thread_id >= Core::Hardware::NUM_CPU_CORES) {
- return GetHostDummyThread();
+ return GetHostDummyThread(nullptr);
}
return current_thread;
@@ -454,6 +526,9 @@ struct KernelCore::Impl {
ASSERT(memory_layout->GetVirtualMemoryRegionTree().Insert(
misc_region_start, misc_region_size, KMemoryRegionType_KernelMisc));
+ // Determine if we'll use extra thread resources.
+ const bool use_extra_resources = KSystemControl::Init::ShouldIncreaseThreadResourceLimit();
+
// Setup the stack region.
constexpr size_t StackRegionSize = 14_MiB;
constexpr size_t StackRegionAlign = KernelAslrAlignment;
@@ -464,7 +539,8 @@ struct KernelCore::Impl {
stack_region_start, StackRegionSize, KMemoryRegionType_KernelStack));
// Determine the size of the resource region.
- const size_t resource_region_size = memory_layout->GetResourceRegionSizeForInit();
+ const size_t resource_region_size =
+ memory_layout->GetResourceRegionSizeForInit(use_extra_resources);
// Determine the size of the slab region.
const size_t slab_region_size =
@@ -698,54 +774,48 @@ struct KernelCore::Impl {
return {};
}
- KClientPort* port = &search->second(system.ServiceManager(), system);
- RegisterServerObject(&port->GetParent()->GetServerPort());
- return port;
+ return &search->second(system.ServiceManager(), system);
}
- void RegisterServerObject(KAutoObject* server_object) {
- std::scoped_lock lk(server_objects_lock);
- server_objects.insert(server_object);
- }
+ void RegisterNamedServiceHandler(std::string name, KServerPort* server_port) {
+ auto search = service_interface_handlers.find(name);
+ if (search == service_interface_handlers.end()) {
+ return;
+ }
- void UnregisterServerObject(KAutoObject* server_object) {
- std::scoped_lock lk(server_objects_lock);
- server_objects.erase(server_object);
+ search->second(system.ServiceManager(), server_port);
}
- std::weak_ptr<Kernel::ServiceThread> CreateServiceThread(KernelCore& kernel,
- const std::string& name) {
- auto service_thread = std::make_shared<Kernel::ServiceThread>(kernel, 1, name);
+ Kernel::ServiceThread& CreateServiceThread(KernelCore& kernel, const std::string& name) {
+ auto* ptr = new ServiceThread(kernel, name);
service_threads_manager.QueueWork(
- [this, service_thread]() { service_threads.emplace(service_thread); });
+ [this, ptr]() { service_threads.emplace(ptr, std::unique_ptr<ServiceThread>(ptr)); });
- return service_thread;
+ return *ptr;
}
- void ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) {
- if (auto strong_ptr = service_thread.lock()) {
- if (strong_ptr == default_service_thread.lock()) {
- // Nothing to do here, the service is using default_service_thread, which will be
- // released on shutdown.
- return;
- }
+ void ReleaseServiceThread(Kernel::ServiceThread& service_thread) {
+ auto* ptr = &service_thread;
- service_threads_manager.QueueWork(
- [this, strong_ptr{std::move(strong_ptr)}]() { service_threads.erase(strong_ptr); });
+ if (ptr == default_service_thread) {
+ // Nothing to do here, the service is using default_service_thread, which will be
+ // released on shutdown.
+ return;
}
+
+ service_threads_manager.QueueWork([this, ptr]() { service_threads.erase(ptr); });
}
void ClearServiceThreads() {
service_threads_manager.QueueWork([this] {
service_threads.clear();
- default_service_thread.reset();
+ default_service_thread = nullptr;
service_thread_barrier.Sync();
});
service_thread_barrier.Sync();
}
- std::mutex server_objects_lock;
std::mutex registered_objects_lock;
std::mutex registered_in_use_objects_lock;
@@ -763,6 +833,8 @@ struct KernelCore::Impl {
Init::KSlabResourceCounts slab_resource_counts{};
KResourceLimit* system_resource_limit{};
+ KPageBufferSlabHeap page_buffer_slab_heap;
+
std::shared_ptr<Core::Timing::EventType> preemption_event;
// This is the kernel's handle table or supervisor handle table which
@@ -774,8 +846,8 @@ struct KernelCore::Impl {
/// Map of named ports managed by the kernel, which can be retrieved using
/// the ConnectToPort SVC.
std::unordered_map<std::string, ServiceInterfaceFactory> service_interface_factory;
+ std::unordered_map<std::string, ServiceInterfaceHandlerFn> service_interface_handlers;
NamedPortTable named_ports;
- std::unordered_set<KAutoObject*> server_objects;
std::unordered_set<KAutoObject*> registered_objects;
std::unordered_set<KAutoObject*> registered_in_use_objects;
@@ -788,10 +860,20 @@ struct KernelCore::Impl {
// Kernel memory management
std::unique_ptr<KMemoryManager> memory_manager;
- // Dynamic slab managers
- std::unique_ptr<KDynamicPageManager> dynamic_page_manager;
- std::unique_ptr<KMemoryBlockSlabHeap> memory_block_heap;
+ // Resource managers
+ std::unique_ptr<KDynamicPageManager> resource_manager_page_manager;
+ std::unique_ptr<KPageTableSlabHeap> page_table_heap;
+ std::unique_ptr<KMemoryBlockSlabHeap> app_memory_block_heap;
+ std::unique_ptr<KMemoryBlockSlabHeap> sys_memory_block_heap;
+ std::unique_ptr<KBlockInfoSlabHeap> block_info_heap;
+ std::unique_ptr<KPageTableManager> app_page_table_manager;
+ std::unique_ptr<KPageTableManager> sys_page_table_manager;
std::unique_ptr<KMemoryBlockSlabManager> app_memory_block_manager;
+ std::unique_ptr<KMemoryBlockSlabManager> sys_memory_block_manager;
+ std::unique_ptr<KBlockInfoManager> app_block_info_manager;
+ std::unique_ptr<KBlockInfoManager> sys_block_info_manager;
+ std::unique_ptr<KSystemResource> app_system_resource;
+ std::unique_ptr<KSystemResource> sys_system_resource;
// Shared memory for services
Kernel::KSharedMemory* hid_shared_mem{};
@@ -804,8 +886,8 @@ struct KernelCore::Impl {
std::unique_ptr<KMemoryLayout> memory_layout;
// Threads used for services
- std::unordered_set<std::shared_ptr<ServiceThread>> service_threads;
- std::weak_ptr<ServiceThread> default_service_thread;
+ std::unordered_map<ServiceThread*, std::unique_ptr<ServiceThread>> service_threads;
+ ServiceThread* default_service_thread{};
Common::ThreadWorker service_threads_manager;
Common::Barrier service_thread_barrier;
@@ -814,7 +896,6 @@ struct KernelCore::Impl {
bool is_multicore{};
std::atomic_bool is_shutting_down{};
- bool is_phantom_mode_for_singlecore{};
u32 single_core_thread_id{};
std::array<u64, Core::Hardware::NUM_CPU_CORES> svc_ticks{};
@@ -981,16 +1062,17 @@ void KernelCore::RegisterNamedService(std::string name, ServiceInterfaceFactory&
impl->service_interface_factory.emplace(std::move(name), factory);
}
-KClientPort* KernelCore::CreateNamedServicePort(std::string name) {
- return impl->CreateNamedServicePort(std::move(name));
+void KernelCore::RegisterInterfaceForNamedService(std::string name,
+ ServiceInterfaceHandlerFn&& handler) {
+ impl->service_interface_handlers.emplace(std::move(name), handler);
}
-void KernelCore::RegisterServerObject(KAutoObject* server_object) {
- impl->RegisterServerObject(server_object);
+KClientPort* KernelCore::CreateNamedServicePort(std::string name) {
+ return impl->CreateNamedServicePort(std::move(name));
}
-void KernelCore::UnregisterServerObject(KAutoObject* server_object) {
- impl->UnregisterServerObject(server_object);
+void KernelCore::RegisterNamedServiceHandler(std::string name, KServerPort* server_port) {
+ impl->RegisterNamedServiceHandler(std::move(name), server_port);
}
void KernelCore::RegisterKernelObject(KAutoObject* object) {
@@ -1045,8 +1127,12 @@ void KernelCore::RegisterCoreThread(std::size_t core_id) {
impl->RegisterCoreThread(core_id);
}
-void KernelCore::RegisterHostThread() {
- impl->RegisterHostThread();
+void KernelCore::RegisterHostThread(KThread* existing_thread) {
+ impl->RegisterHostThread(existing_thread);
+
+ if (existing_thread != nullptr) {
+ ASSERT(GetCurrentEmuThread() == existing_thread);
+ }
}
u32 KernelCore::GetCurrentHostThreadID() const {
@@ -1069,12 +1155,12 @@ const KMemoryManager& KernelCore::MemoryManager() const {
return *impl->memory_manager;
}
-KMemoryBlockSlabManager& KernelCore::GetApplicationMemoryBlockManager() {
- return *impl->app_memory_block_manager;
+KSystemResource& KernelCore::GetSystemSystemResource() {
+ return *impl->sys_system_resource;
}
-const KMemoryBlockSlabManager& KernelCore::GetApplicationMemoryBlockManager() const {
- return *impl->app_memory_block_manager;
+const KSystemResource& KernelCore::GetSystemSystemResource() const {
+ return *impl->sys_system_resource;
}
Kernel::KSharedMemory& KernelCore::GetHidSharedMem() {
@@ -1121,16 +1207,28 @@ void KernelCore::Suspend(bool suspended) {
const bool should_suspend{exception_exited || suspended};
const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable;
- for (auto* process : GetProcessList()) {
- process->SetActivity(activity);
+ std::vector<KScopedAutoObject<KThread>> process_threads;
+ {
+ KScopedSchedulerLock sl{*this};
+
+ if (auto* process = CurrentProcess(); process != nullptr) {
+ process->SetActivity(activity);
+
+ if (!should_suspend) {
+ // Runnable now; no need to wait.
+ return;
+ }
- if (should_suspend) {
- // Wait for execution to stop
for (auto* thread : process->GetThreadList()) {
- thread->WaitUntilSuspended();
+ process_threads.emplace_back(thread);
}
}
}
+
+ // Wait for execution to stop.
+ for (auto& thread : process_threads) {
+ thread->WaitUntilSuspended();
+ }
}
void KernelCore::ShutdownCores() {
@@ -1162,15 +1260,15 @@ void KernelCore::ExitSVCProfile() {
MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[CurrentPhysicalCoreIndex()]);
}
-std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) {
+Kernel::ServiceThread& KernelCore::CreateServiceThread(const std::string& name) {
return impl->CreateServiceThread(*this, name);
}
-std::weak_ptr<Kernel::ServiceThread> KernelCore::GetDefaultServiceThread() const {
- return impl->default_service_thread;
+Kernel::ServiceThread& KernelCore::GetDefaultServiceThread() const {
+ return *impl->default_service_thread;
}
-void KernelCore::ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) {
+void KernelCore::ReleaseServiceThread(Kernel::ServiceThread& service_thread) {
impl->ReleaseServiceThread(service_thread);
}
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 266be2bc4..2e22fe0f6 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -34,22 +34,27 @@ class KClientPort;
class GlobalSchedulerContext;
class KAutoObjectWithListContainer;
class KClientSession;
+class KDebug;
+class KDynamicPageManager;
class KEvent;
+class KEventInfo;
class KHandleTable;
class KLinkedListNode;
-class KMemoryBlockSlabManager;
class KMemoryLayout;
class KMemoryManager;
class KPageBuffer;
+class KPageBufferSlabHeap;
class KPort;
class KProcess;
class KResourceLimit;
class KScheduler;
+class KServerPort;
class KServerSession;
class KSession;
class KSessionRequest;
class KSharedMemory;
class KSharedMemoryInfo;
+class KSecureSystemResource;
class KThread;
class KThreadLocalPage;
class KTransferMemory;
@@ -63,6 +68,8 @@ class TimeManager;
using ServiceInterfaceFactory =
std::function<KClientPort&(Service::SM::ServiceManager&, Core::System&)>;
+using ServiceInterfaceHandlerFn = std::function<void(Service::SM::ServiceManager&, KServerPort*)>;
+
namespace Init {
struct KSlabResourceCounts;
}
@@ -192,16 +199,14 @@ public:
/// Registers a named HLE service, passing a factory used to open a port to that service.
void RegisterNamedService(std::string name, ServiceInterfaceFactory&& factory);
+ /// Registers a setup function for the named HLE service.
+ void RegisterInterfaceForNamedService(std::string name, ServiceInterfaceHandlerFn&& handler);
+
/// Opens a port to a service previously registered with RegisterNamedService.
KClientPort* CreateNamedServicePort(std::string name);
- /// Registers a server session or port with the gobal emulation state, to be freed on shutdown.
- /// This is necessary because we do not emulate processes for HLE sessions and ports.
- void RegisterServerObject(KAutoObject* server_object);
-
- /// Unregisters a server session or port previously registered with RegisterServerSession when
- /// it was destroyed during the current emulation session.
- void UnregisterServerObject(KAutoObject* server_object);
+ /// Accepts a session on a port created by CreateNamedServicePort.
+ void RegisterNamedServiceHandler(std::string name, KServerPort* server_port);
/// Registers all kernel objects with the global emulation state, this is purely for tracking
/// leaks after emulation has been shutdown.
@@ -235,7 +240,7 @@ public:
void RegisterCoreThread(std::size_t core_id);
/// Register the current thread as a non CPU core thread.
- void RegisterHostThread();
+ void RegisterHostThread(KThread* existing_thread = nullptr);
/// Gets the virtual memory manager for the kernel.
KMemoryManager& MemoryManager();
@@ -243,11 +248,11 @@ public:
/// Gets the virtual memory manager for the kernel.
const KMemoryManager& MemoryManager() const;
- /// Gets the application memory block manager for the kernel.
- KMemoryBlockSlabManager& GetApplicationMemoryBlockManager();
+ /// Gets the system resource manager.
+ KSystemResource& GetSystemSystemResource();
- /// Gets the application memory block manager for the kernel.
- const KMemoryBlockSlabManager& GetApplicationMemoryBlockManager() const;
+ /// Gets the system resource manager.
+ const KSystemResource& GetSystemSystemResource() const;
/// Gets the shared memory object for HID services.
Kernel::KSharedMemory& GetHidSharedMem();
@@ -304,24 +309,24 @@ public:
* See GetDefaultServiceThread.
* @param name String name for the ServerSession creating this thread, used for debug
* purposes.
- * @returns The a weak pointer newly created service thread.
+ * @returns A reference to the newly created service thread.
*/
- std::weak_ptr<Kernel::ServiceThread> CreateServiceThread(const std::string& name);
+ Kernel::ServiceThread& CreateServiceThread(const std::string& name);
/**
* Gets the default host service thread, which executes HLE service requests. Unless service
* requests need to block on the host, the default service thread should be used in favor of
* creating a new service thread.
- * @returns The a weak pointer for the default service thread.
+ * @returns A reference to the default service thread.
*/
- std::weak_ptr<Kernel::ServiceThread> GetDefaultServiceThread() const;
+ Kernel::ServiceThread& GetDefaultServiceThread() const;
/**
* Releases a HLE service thread, instructing KernelCore to free it. This should be called when
* the ServerSession associated with the thread is destroyed.
* @param service_thread Service thread to release.
*/
- void ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread);
+ void ReleaseServiceThread(Kernel::ServiceThread& service_thread);
/// Workaround for single-core mode when preempting threads while idle.
bool IsPhantomModeForSingleCore() const;
@@ -363,6 +368,12 @@ public:
return slab_heap_container->thread_local_page;
} else if constexpr (std::is_same_v<T, KSessionRequest>) {
return slab_heap_container->session_request;
+ } else if constexpr (std::is_same_v<T, KSecureSystemResource>) {
+ return slab_heap_container->secure_system_resource;
+ } else if constexpr (std::is_same_v<T, KEventInfo>) {
+ return slab_heap_container->event_info;
+ } else if constexpr (std::is_same_v<T, KDebug>) {
+ return slab_heap_container->debug;
}
}
@@ -426,6 +437,9 @@ private:
KSlabHeap<KPageBuffer> page_buffer;
KSlabHeap<KThreadLocalPage> thread_local_page;
KSlabHeap<KSessionRequest> session_request;
+ KSlabHeap<KSecureSystemResource> secure_system_resource;
+ KSlabHeap<KEventInfo> event_info;
+ KSlabHeap<KDebug> debug;
};
std::unique_ptr<SlabHeapContainer> slab_heap_container;
diff --git a/src/core/hle/kernel/physical_core.cpp b/src/core/hle/kernel/physical_core.cpp
index d4375962f..3044922ac 100644
--- a/src/core/hle/kernel/physical_core.cpp
+++ b/src/core/hle/kernel/physical_core.cpp
@@ -12,7 +12,7 @@ namespace Kernel {
PhysicalCore::PhysicalCore(std::size_t core_index_, Core::System& system_, KScheduler& scheduler_)
: core_index{core_index_}, system{system_}, scheduler{scheduler_} {
-#ifdef ARCHITECTURE_x86_64
+#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
// TODO(bunnei): Initialization relies on a core being available. We may later replace this with
// a 32-bit instance of Dynarmic. This should be abstracted out to a CPU manager.
auto& kernel = system.Kernel();
@@ -26,7 +26,7 @@ PhysicalCore::PhysicalCore(std::size_t core_index_, Core::System& system_, KSche
PhysicalCore::~PhysicalCore() = default;
void PhysicalCore::Initialize([[maybe_unused]] bool is_64_bit) {
-#ifdef ARCHITECTURE_x86_64
+#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64)
auto& kernel = system.Kernel();
if (!is_64_bit) {
// We already initialized a 64-bit core, replace with a 32-bit one.
diff --git a/src/core/hle/kernel/service_thread.cpp b/src/core/hle/kernel/service_thread.cpp
index d23d76706..f5c2ab23f 100644
--- a/src/core/hle/kernel/service_thread.cpp
+++ b/src/core/hle/kernel/service_thread.cpp
@@ -1,15 +1,18 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <condition_variable>
#include <functional>
+#include <map>
#include <mutex>
#include <thread>
#include <vector>
-#include <queue>
#include "common/scope_exit.h"
#include "common/thread.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_session.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
@@ -19,101 +22,210 @@ namespace Kernel {
class ServiceThread::Impl final {
public:
- explicit Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name);
+ explicit Impl(KernelCore& kernel, const std::string& service_name);
~Impl();
- void QueueSyncRequest(KSession& session, std::shared_ptr<HLERequestContext>&& context);
+ void WaitAndProcessImpl();
+ void SessionClosed(KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager> manager);
+ void LoopProcess();
+
+ void RegisterServerSession(KServerSession* session,
+ std::shared_ptr<SessionRequestManager> manager);
private:
- std::vector<std::jthread> threads;
- std::queue<std::function<void()>> requests;
- std::mutex queue_mutex;
- std::condition_variable_any condition;
- const std::string service_name;
+ KernelCore& kernel;
+
+ std::jthread m_host_thread;
+ std::mutex m_session_mutex;
+ std::map<KServerSession*, std::shared_ptr<SessionRequestManager>> m_sessions;
+ KEvent* m_wakeup_event;
+ KProcess* m_process;
+ KThread* m_thread;
+ std::atomic<bool> m_shutdown_requested;
+ const std::string m_service_name;
};
-ServiceThread::Impl::Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name)
- : service_name{name} {
- for (std::size_t i = 0; i < num_threads; ++i) {
- threads.emplace_back([this, &kernel](std::stop_token stop_token) {
- Common::SetCurrentThreadName(std::string{service_name}.c_str());
+void ServiceThread::Impl::WaitAndProcessImpl() {
+ // Create local list of waitable sessions.
+ std::vector<KSynchronizationObject*> objs;
+ std::vector<std::shared_ptr<SessionRequestManager>> managers;
+
+ {
+ // Lock to get the set.
+ std::scoped_lock lk{m_session_mutex};
+
+ // Reserve the needed quantity.
+ objs.reserve(m_sessions.size() + 1);
+ managers.reserve(m_sessions.size());
+
+ // Copy to our local list.
+ for (const auto& [session, manager] : m_sessions) {
+ objs.push_back(session);
+ managers.push_back(manager);
+ }
- // Wait for first request before trying to acquire a render context
- {
- std::unique_lock lock{queue_mutex};
- condition.wait(lock, stop_token, [this] { return !requests.empty(); });
- }
+ // Insert the wakeup event at the end.
+ objs.push_back(&m_wakeup_event->GetReadableEvent());
+ }
+
+ // Wait on the list of sessions.
+ s32 index{-1};
+ Result rc = KSynchronizationObject::Wait(kernel, &index, objs.data(),
+ static_cast<s32>(objs.size()), -1);
+ ASSERT(!rc.IsFailure());
+
+ // If this was the wakeup event, clear it and finish.
+ if (index >= static_cast<s64>(objs.size() - 1)) {
+ m_wakeup_event->Clear();
+ return;
+ }
- if (stop_token.stop_requested()) {
- return;
- }
+ // This event is from a server session.
+ auto* server_session = static_cast<KServerSession*>(objs[index]);
+ auto& manager = managers[index];
- // Allocate a dummy guest thread for this host thread.
- kernel.RegisterHostThread();
+ // Fetch the HLE request context.
+ std::shared_ptr<HLERequestContext> context;
+ rc = server_session->ReceiveRequest(&context, manager);
- while (true) {
- std::function<void()> task;
+ // If the session was closed, handle that.
+ if (rc == ResultSessionClosed) {
+ SessionClosed(server_session, manager);
- {
- std::unique_lock lock{queue_mutex};
- condition.wait(lock, stop_token, [this] { return !requests.empty(); });
+ // Finish.
+ return;
+ }
- if (stop_token.stop_requested()) {
- return;
- }
+ // TODO: handle other cases
+ ASSERT(rc == ResultSuccess);
- if (requests.empty()) {
- continue;
- }
+ // Perform the request.
+ Result service_rc = manager->CompleteSyncRequest(server_session, *context);
- task = std::move(requests.front());
- requests.pop();
- }
+ // Reply to the client.
+ rc = server_session->SendReplyHLE();
- task();
- }
- });
+ if (rc == ResultSessionClosed || service_rc == IPC::ERR_REMOTE_PROCESS_DEAD) {
+ SessionClosed(server_session, manager);
+ return;
}
+
+ // TODO: handle other cases
+ ASSERT(rc == ResultSuccess);
+ ASSERT(service_rc == ResultSuccess);
}
-void ServiceThread::Impl::QueueSyncRequest(KSession& session,
- std::shared_ptr<HLERequestContext>&& context) {
+void ServiceThread::Impl::SessionClosed(KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager> manager) {
{
- std::unique_lock lock{queue_mutex};
+ // Lock to get the set.
+ std::scoped_lock lk{m_session_mutex};
- auto* server_session{&session.GetServerSession()};
+ // Erase the session.
+ ASSERT(m_sessions.erase(server_session) == 1);
+ }
+
+ // Close our reference to the server session.
+ server_session->Close();
+}
+
+void ServiceThread::Impl::LoopProcess() {
+ Common::SetCurrentThreadName(m_service_name.c_str());
+
+ kernel.RegisterHostThread(m_thread);
+
+ while (!m_shutdown_requested.load()) {
+ WaitAndProcessImpl();
+ }
+}
- // Open a reference to the session to ensure it is not closes while the service request
- // completes asynchronously.
- server_session->Open();
+void ServiceThread::Impl::RegisterServerSession(KServerSession* server_session,
+ std::shared_ptr<SessionRequestManager> manager) {
+ // Open the server session.
+ server_session->Open();
- requests.emplace([server_session, context{std::move(context)}]() {
- // Close the reference.
- SCOPE_EXIT({ server_session->Close(); });
+ {
+ // Lock to get the set.
+ std::scoped_lock lk{m_session_mutex};
- // Complete the service request.
- server_session->CompleteSyncRequest(*context);
- });
+ // Insert the session and manager.
+ m_sessions[server_session] = manager;
}
- condition.notify_one();
+
+ // Signal the wakeup event.
+ m_wakeup_event->Signal();
}
ServiceThread::Impl::~Impl() {
- condition.notify_all();
- for (auto& thread : threads) {
- thread.request_stop();
- thread.join();
+ // Shut down the processing thread.
+ m_shutdown_requested.store(true);
+ m_wakeup_event->Signal();
+ m_host_thread.join();
+
+ // Lock mutex.
+ m_session_mutex.lock();
+
+ // Close all remaining sessions.
+ for (const auto& [server_session, manager] : m_sessions) {
+ server_session->Close();
}
+
+ // Destroy remaining managers.
+ m_sessions.clear();
+
+ // Close event.
+ m_wakeup_event->GetReadableEvent().Close();
+ m_wakeup_event->Close();
+
+ // Close thread.
+ m_thread->Close();
+
+ // Close process.
+ m_process->Close();
+}
+
+ServiceThread::Impl::Impl(KernelCore& kernel_, const std::string& service_name)
+ : kernel{kernel_}, m_service_name{service_name} {
+ // Initialize process.
+ m_process = KProcess::Create(kernel);
+ KProcess::Initialize(m_process, kernel.System(), service_name,
+ KProcess::ProcessType::KernelInternal, kernel.GetSystemResourceLimit());
+
+ // Reserve a new event from the process resource limit
+ KScopedResourceReservation event_reservation(m_process, LimitableResource::EventCountMax);
+ ASSERT(event_reservation.Succeeded());
+
+ // Initialize event.
+ m_wakeup_event = KEvent::Create(kernel);
+ m_wakeup_event->Initialize(m_process);
+
+ // Commit the event reservation.
+ event_reservation.Commit();
+
+ // Reserve a new thread from the process resource limit
+ KScopedResourceReservation thread_reservation(m_process, LimitableResource::ThreadCountMax);
+ ASSERT(thread_reservation.Succeeded());
+
+ // Initialize thread.
+ m_thread = KThread::Create(kernel);
+ ASSERT(KThread::InitializeDummyThread(m_thread, m_process).IsSuccess());
+
+ // Commit the thread reservation.
+ thread_reservation.Commit();
+
+ // Start thread.
+ m_host_thread = std::jthread([this] { LoopProcess(); });
}
-ServiceThread::ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name)
- : impl{std::make_unique<Impl>(kernel, num_threads, name)} {}
+ServiceThread::ServiceThread(KernelCore& kernel, const std::string& name)
+ : impl{std::make_unique<Impl>(kernel, name)} {}
ServiceThread::~ServiceThread() = default;
-void ServiceThread::QueueSyncRequest(KSession& session,
- std::shared_ptr<HLERequestContext>&& context) {
- impl->QueueSyncRequest(session, std::move(context));
+void ServiceThread::RegisterServerSession(KServerSession* session,
+ std::shared_ptr<SessionRequestManager> manager) {
+ impl->RegisterServerSession(session, manager);
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/service_thread.h b/src/core/hle/kernel/service_thread.h
index c5896f2bd..fb4325531 100644
--- a/src/core/hle/kernel/service_thread.h
+++ b/src/core/hle/kernel/service_thread.h
@@ -11,13 +11,15 @@ namespace Kernel {
class HLERequestContext;
class KernelCore;
class KSession;
+class SessionRequestManager;
class ServiceThread final {
public:
- explicit ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name);
+ explicit ServiceThread(KernelCore& kernel, const std::string& name);
~ServiceThread();
- void QueueSyncRequest(KSession& session, std::shared_ptr<HLERequestContext>&& context);
+ void RegisterServerSession(KServerSession* session,
+ std::shared_ptr<SessionRequestManager> manager);
private:
class Impl;
diff --git a/src/core/hle/kernel/slab_helpers.h b/src/core/hle/kernel/slab_helpers.h
index 06b51e919..0228ce188 100644
--- a/src/core/hle/kernel/slab_helpers.h
+++ b/src/core/hle/kernel/slab_helpers.h
@@ -53,6 +53,84 @@ public:
};
template <typename Derived, typename Base>
+class KAutoObjectWithSlabHeap : public Base {
+ static_assert(std::is_base_of<KAutoObject, Base>::value);
+
+private:
+ static Derived* Allocate(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().Allocate(kernel);
+ }
+
+ static void Free(KernelCore& kernel, Derived* obj) {
+ kernel.SlabHeap<Derived>().Free(obj);
+ }
+
+public:
+ explicit KAutoObjectWithSlabHeap(KernelCore& kernel_) : Base(kernel_), kernel(kernel_) {}
+ virtual ~KAutoObjectWithSlabHeap() = default;
+
+ virtual void Destroy() override {
+ const bool is_initialized = this->IsInitialized();
+ uintptr_t arg = 0;
+ if (is_initialized) {
+ arg = this->GetPostDestroyArgument();
+ this->Finalize();
+ }
+ Free(kernel, static_cast<Derived*>(this));
+ if (is_initialized) {
+ Derived::PostDestroy(arg);
+ }
+ }
+
+ virtual bool IsInitialized() const {
+ return true;
+ }
+ virtual uintptr_t GetPostDestroyArgument() const {
+ return 0;
+ }
+
+ size_t GetSlabIndex() const {
+ return SlabHeap<Derived>(kernel).GetObjectIndex(static_cast<const Derived*>(this));
+ }
+
+public:
+ static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) {
+ kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
+ }
+
+ static Derived* Create(KernelCore& kernel) {
+ Derived* obj = Allocate(kernel);
+ if (obj != nullptr) {
+ KAutoObject::Create(obj);
+ }
+ return obj;
+ }
+
+ static size_t GetObjectSize(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().GetObjectSize();
+ }
+
+ static size_t GetSlabHeapSize(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().GetSlabHeapSize();
+ }
+
+ static size_t GetPeakIndex(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().GetPeakIndex();
+ }
+
+ static uintptr_t GetSlabHeapAddress(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().GetSlabHeapAddress();
+ }
+
+ static size_t GetNumRemaining(KernelCore& kernel) {
+ return kernel.SlabHeap<Derived>().GetNumRemaining();
+ }
+
+protected:
+ KernelCore& kernel;
+};
+
+template <typename Derived, typename Base>
class KAutoObjectWithSlabHeapAndContainer : public Base {
static_assert(std::is_base_of<KAutoObjectWithList, Base>::value);
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 4aca5b27d..9962ad171 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -24,6 +24,7 @@
#include "core/hle/kernel/k_memory_block.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_page_table.h"
+#include "core/hle/kernel/k_port.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_resource_limit.h"
@@ -266,7 +267,7 @@ Result CreateSession(Core::System& system, Handle* out_server, Handle* out_clien
// Reserve a new session from the process resource limit.
// FIXME: LimitableResource_SessionCountMax
- KScopedResourceReservation session_reservation(&process, LimitableResource::Sessions);
+ KScopedResourceReservation session_reservation(&process, LimitableResource::SessionCountMax);
if (session_reservation.Succeeded()) {
session = T::Create(system.Kernel());
} else {
@@ -297,7 +298,7 @@ Result CreateSession(Core::System& system, Handle* out_server, Handle* out_clien
// We successfully allocated a session, so add the object we allocated to the resource
// limit.
- // system.Kernel().GetSystemResourceLimit().Reserve(LimitableResource::Sessions, 1);
+ // system.Kernel().GetSystemResourceLimit().Reserve(LimitableResource::SessionCountMax, 1);
}
// Check that we successfully created a session.
@@ -382,9 +383,9 @@ static Result ConnectToNamedPort(Core::System& system, Handle* out, VAddr port_n
// Create a session.
KClientSession* session{};
- R_TRY(port->CreateSession(std::addressof(session),
- std::make_shared<SessionRequestManager>(kernel)));
- port->Close();
+ R_TRY(port->CreateSession(std::addressof(session)));
+
+ kernel.RegisterNamedServiceHandler(port_name, &port->GetParent()->GetServerPort());
// Register the session in the table, close the extra reference.
handle_table.Register(*out, session);
@@ -655,27 +656,12 @@ static Result ArbitrateUnlock32(Core::System& system, u32 address) {
return ArbitrateUnlock(system, address);
}
-enum class BreakType : u32 {
- Panic = 0,
- AssertionFailed = 1,
- PreNROLoad = 3,
- PostNROLoad = 4,
- PreNROUnload = 5,
- PostNROUnload = 6,
- CppException = 7,
-};
-
-struct BreakReason {
- union {
- u32 raw;
- BitField<0, 30, BreakType> break_type;
- BitField<31, 1, u32> signal_debugger;
- };
-};
-
/// Break program execution
static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
- BreakReason break_reason{reason};
+ BreakReason break_reason =
+ static_cast<BreakReason>(reason & ~static_cast<u32>(BreakReason::NotificationOnlyFlag));
+ bool notification_only = (reason & static_cast<u32>(BreakReason::NotificationOnlyFlag)) != 0;
+
bool has_dumped_buffer{};
std::vector<u8> debug_buffer;
@@ -704,57 +690,56 @@ static void Break(Core::System& system, u32 reason, u64 info1, u64 info2) {
}
has_dumped_buffer = true;
};
- switch (break_reason.break_type) {
- case BreakType::Panic:
- LOG_CRITICAL(Debug_Emulated, "Signalling debugger, PANIC! info1=0x{:016X}, info2=0x{:016X}",
- info1, info2);
+ switch (break_reason) {
+ case BreakReason::Panic:
+ LOG_CRITICAL(Debug_Emulated, "Userspace PANIC! info1=0x{:016X}, info2=0x{:016X}", info1,
+ info2);
handle_debug_buffer(info1, info2);
break;
- case BreakType::AssertionFailed:
- LOG_CRITICAL(Debug_Emulated,
- "Signalling debugger, Assertion failed! info1=0x{:016X}, info2=0x{:016X}",
+ case BreakReason::Assert:
+ LOG_CRITICAL(Debug_Emulated, "Userspace Assertion failed! info1=0x{:016X}, info2=0x{:016X}",
info1, info2);
handle_debug_buffer(info1, info2);
break;
- case BreakType::PreNROLoad:
- LOG_WARNING(
- Debug_Emulated,
- "Signalling debugger, Attempting to load an NRO at 0x{:016X} with size 0x{:016X}",
- info1, info2);
+ case BreakReason::User:
+ LOG_WARNING(Debug_Emulated, "Userspace Break! 0x{:016X} with size 0x{:016X}", info1, info2);
+ handle_debug_buffer(info1, info2);
break;
- case BreakType::PostNROLoad:
- LOG_WARNING(Debug_Emulated,
- "Signalling debugger, Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1,
- info2);
+ case BreakReason::PreLoadDll:
+ LOG_INFO(Debug_Emulated,
+ "Userspace Attempting to load an NRO at 0x{:016X} with size 0x{:016X}", info1,
+ info2);
break;
- case BreakType::PreNROUnload:
- LOG_WARNING(
- Debug_Emulated,
- "Signalling debugger, Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}",
- info1, info2);
+ case BreakReason::PostLoadDll:
+ LOG_INFO(Debug_Emulated, "Userspace Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1,
+ info2);
break;
- case BreakType::PostNROUnload:
- LOG_WARNING(Debug_Emulated,
- "Signalling debugger, Unloaded an NRO at 0x{:016X} with size 0x{:016X}", info1,
- info2);
+ case BreakReason::PreUnloadDll:
+ LOG_INFO(Debug_Emulated,
+ "Userspace Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}", info1,
+ info2);
break;
- case BreakType::CppException:
+ case BreakReason::PostUnloadDll:
+ LOG_INFO(Debug_Emulated, "Userspace Unloaded an NRO at 0x{:016X} with size 0x{:016X}",
+ info1, info2);
+ break;
+ case BreakReason::CppException:
LOG_CRITICAL(Debug_Emulated, "Signalling debugger. Uncaught C++ exception encountered.");
break;
default:
LOG_WARNING(
Debug_Emulated,
- "Signalling debugger, Unknown break reason {}, info1=0x{:016X}, info2=0x{:016X}",
- static_cast<u32>(break_reason.break_type.Value()), info1, info2);
+ "Signalling debugger, Unknown break reason {:#X}, info1=0x{:016X}, info2=0x{:016X}",
+ reason, info1, info2);
handle_debug_buffer(info1, info2);
break;
}
- system.GetReporter().SaveSvcBreakReport(
- static_cast<u32>(break_reason.break_type.Value()), break_reason.signal_debugger.As<bool>(),
- info1, info2, has_dumped_buffer ? std::make_optional(debug_buffer) : std::nullopt);
+ system.GetReporter().SaveSvcBreakReport(reason, notification_only, info1, info2,
+ has_dumped_buffer ? std::make_optional(debug_buffer)
+ : std::nullopt);
- if (!break_reason.signal_debugger) {
+ if (!notification_only) {
LOG_CRITICAL(
Debug_Emulated,
"Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
@@ -1715,13 +1700,13 @@ static Result QueryProcessMemory(Core::System& system, VAddr memory_info_address
auto& memory{system.Memory()};
const auto memory_info{process->PageTable().QueryInfo(address).GetSvcMemoryInfo()};
- memory.Write64(memory_info_address + 0x00, memory_info.addr);
+ memory.Write64(memory_info_address + 0x00, memory_info.base_address);
memory.Write64(memory_info_address + 0x08, memory_info.size);
memory.Write32(memory_info_address + 0x10, static_cast<u32>(memory_info.state) & 0xff);
- memory.Write32(memory_info_address + 0x14, static_cast<u32>(memory_info.attr));
- memory.Write32(memory_info_address + 0x18, static_cast<u32>(memory_info.perm));
- memory.Write32(memory_info_address + 0x1c, memory_info.ipc_refcount);
- memory.Write32(memory_info_address + 0x20, memory_info.device_refcount);
+ memory.Write32(memory_info_address + 0x14, static_cast<u32>(memory_info.attribute));
+ memory.Write32(memory_info_address + 0x18, static_cast<u32>(memory_info.permission));
+ memory.Write32(memory_info_address + 0x1c, memory_info.ipc_count);
+ memory.Write32(memory_info_address + 0x20, memory_info.device_count);
memory.Write32(memory_info_address + 0x24, 0);
// Page info appears to be currently unused by the kernel and is always set to zero.
@@ -1942,7 +1927,7 @@ static Result CreateThread(Core::System& system, Handle* out_handle, VAddr entry
// Reserve a new thread from the process resource limit (waiting up to 100ms).
KScopedResourceReservation thread_reservation(
- kernel.CurrentProcess(), LimitableResource::Threads, 1,
+ kernel.CurrentProcess(), LimitableResource::ThreadCountMax, 1,
system.CoreTiming().GetGlobalTimeNs().count() + 100000000);
if (!thread_reservation.Succeeded()) {
LOG_ERROR(Kernel_SVC, "Could not reserve a new thread");
@@ -2246,7 +2231,7 @@ static u64 GetSystemTick(Core::System& system) {
auto& core_timing = system.CoreTiming();
// Returns the value of cntpct_el0 (https://switchbrew.org/wiki/SVC#svcGetSystemTick)
- const u64 result{system.CoreTiming().GetClockTicks()};
+ const u64 result{core_timing.GetClockTicks()};
if (!system.Kernel().IsMulticore()) {
core_timing.AddTicks(400U);
@@ -2343,7 +2328,7 @@ static Result CreateTransferMemory(Core::System& system, Handle* out, VAddr addr
// Reserve a new transfer memory from the process resource limit.
KScopedResourceReservation trmem_reservation(kernel.CurrentProcess(),
- LimitableResource::TransferMemory);
+ LimitableResource::TransferMemoryCountMax);
R_UNLESS(trmem_reservation.Succeeded(), ResultLimitReached);
// Create the transfer memory.
@@ -2495,7 +2480,7 @@ static Result CreateEvent(Core::System& system, Handle* out_write, Handle* out_r
// Reserve a new event from the process resource limit
KScopedResourceReservation event_reservation(kernel.CurrentProcess(),
- LimitableResource::Events);
+ LimitableResource::EventCountMax);
R_UNLESS(event_reservation.Succeeded(), ResultLimitReached);
// Create a new event.
@@ -2538,11 +2523,6 @@ static Result CreateEvent32(Core::System& system, Handle* out_write, Handle* out
static Result GetProcessInfo(Core::System& system, u64* out, Handle process_handle, u32 type) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type);
- // This function currently only allows retrieving a process' status.
- enum class InfoType {
- Status,
- };
-
const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
KScopedAutoObject process = handle_table.GetObject<KProcess>(process_handle);
if (process.IsNull()) {
@@ -2551,9 +2531,9 @@ static Result GetProcessInfo(Core::System& system, u64* out, Handle process_hand
return ResultInvalidHandle;
}
- const auto info_type = static_cast<InfoType>(type);
- if (info_type != InfoType::Status) {
- LOG_ERROR(Kernel_SVC, "Expected info_type to be Status but got {} instead", type);
+ const auto info_type = static_cast<ProcessInfoType>(type);
+ if (info_type != ProcessInfoType::ProcessState) {
+ LOG_ERROR(Kernel_SVC, "Expected info_type to be ProcessState but got {} instead", type);
return ResultInvalidEnumValue;
}
diff --git a/src/core/hle/kernel/svc_results.h b/src/core/hle/kernel/svc_results.h
index f27cade33..b7ca53085 100644
--- a/src/core/hle/kernel/svc_results.h
+++ b/src/core/hle/kernel/svc_results.h
@@ -37,6 +37,7 @@ constexpr Result ResultInvalidState{ErrorModule::Kernel, 125};
constexpr Result ResultReservedUsed{ErrorModule::Kernel, 126};
constexpr Result ResultPortClosed{ErrorModule::Kernel, 131};
constexpr Result ResultLimitReached{ErrorModule::Kernel, 132};
+constexpr Result ResultOutOfAddressSpace{ErrorModule::Kernel, 259};
constexpr Result ResultInvalidId{ErrorModule::Kernel, 519};
} // namespace Kernel
diff --git a/src/core/hle/kernel/svc_types.h b/src/core/hle/kernel/svc_types.h
index abb9847fe..33eebcef6 100644
--- a/src/core/hle/kernel/svc_types.h
+++ b/src/core/hle/kernel/svc_types.h
@@ -8,6 +8,8 @@
namespace Kernel::Svc {
+using Handle = u32;
+
enum class MemoryState : u32 {
Free = 0x00,
Io = 0x01,
@@ -22,8 +24,8 @@ enum class MemoryState : u32 {
Ipc = 0x0A,
Stack = 0x0B,
ThreadLocal = 0x0C,
- Transferred = 0x0D,
- SharedTransferred = 0x0E,
+ Transfered = 0x0D,
+ SharedTransfered = 0x0E,
SharedCode = 0x0F,
Inaccessible = 0x10,
NonSecureIpc = 0x11,
@@ -32,6 +34,7 @@ enum class MemoryState : u32 {
GeneratedCode = 0x14,
CodeOut = 0x15,
Coverage = 0x16,
+ Insecure = 0x17,
};
DECLARE_ENUM_FLAG_OPERATORS(MemoryState);
@@ -54,17 +57,6 @@ enum class MemoryPermission : u32 {
};
DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission);
-struct MemoryInfo {
- u64 addr{};
- u64 size{};
- MemoryState state{};
- MemoryAttribute attr{};
- MemoryPermission perm{};
- u32 ipc_refcount{};
- u32 device_refcount{};
- u32 padding{};
-};
-
enum class SignalType : u32 {
Signal = 0,
SignalAndIncrementIfEqual = 1,
@@ -83,6 +75,13 @@ enum class YieldType : s64 {
ToAnyThread = -2,
};
+enum class ThreadExitReason : u32 {
+ ExitThread = 0,
+ TerminateThread = 1,
+ ExitProcess = 2,
+ TerminateProcess = 3,
+};
+
enum class ThreadActivity : u32 {
Runnable = 0,
Paused = 1,
@@ -108,6 +107,489 @@ enum class ProcessState : u32 {
DebugBreak = 7,
};
+enum class ProcessExitReason : u32 {
+ ExitProcess = 0,
+ TerminateProcess = 1,
+ Exception = 2,
+};
+
constexpr inline size_t ThreadLocalRegionSize = 0x200;
+struct PageInfo {
+ u32 flags;
+};
+
+// Info Types.
+enum class InfoType : u32 {
+ CoreMask = 0,
+ PriorityMask = 1,
+ AliasRegionAddress = 2,
+ AliasRegionSize = 3,
+ HeapRegionAddress = 4,
+ HeapRegionSize = 5,
+ TotalMemorySize = 6,
+ UsedMemorySize = 7,
+ DebuggerAttached = 8,
+ ResourceLimit = 9,
+ IdleTickCount = 10,
+ RandomEntropy = 11,
+ AslrRegionAddress = 12,
+ AslrRegionSize = 13,
+ StackRegionAddress = 14,
+ StackRegionSize = 15,
+ SystemResourceSizeTotal = 16,
+ SystemResourceSizeUsed = 17,
+ ProgramId = 18,
+ InitialProcessIdRange = 19,
+ UserExceptionContextAddress = 20,
+ TotalNonSystemMemorySize = 21,
+ UsedNonSystemMemorySize = 22,
+ IsApplication = 23,
+ FreeThreadCount = 24,
+ ThreadTickCount = 25,
+ IsSvcPermitted = 26,
+
+ MesosphereMeta = 65000,
+ MesosphereCurrentProcess = 65001,
+};
+
+enum class BreakReason : u32 {
+ Panic = 0,
+ Assert = 1,
+ User = 2,
+ PreLoadDll = 3,
+ PostLoadDll = 4,
+ PreUnloadDll = 5,
+ PostUnloadDll = 6,
+ CppException = 7,
+
+ NotificationOnlyFlag = 0x80000000,
+};
+
+enum class DebugEvent : u32 {
+ CreateProcess = 0,
+ CreateThread = 1,
+ ExitProcess = 2,
+ ExitThread = 3,
+ Exception = 4,
+};
+
+enum class DebugThreadParam : u32 {
+ Priority = 0,
+ State = 1,
+ IdealCore = 2,
+ CurrentCore = 3,
+ AffinityMask = 4,
+};
+
+enum class DebugException : u32 {
+ UndefinedInstruction = 0,
+ InstructionAbort = 1,
+ DataAbort = 2,
+ AlignmentFault = 3,
+ DebuggerAttached = 4,
+ BreakPoint = 5,
+ UserBreak = 6,
+ DebuggerBreak = 7,
+ UndefinedSystemCall = 8,
+ MemorySystemError = 9,
+};
+
+enum class DebugEventFlag : u32 {
+ Stopped = (1u << 0),
+};
+
+enum class BreakPointType : u32 {
+ HardwareInstruction = 0,
+ HardwareData = 1,
+};
+
+enum class HardwareBreakPointRegisterName : u32 {
+ I0 = 0,
+ I1 = 1,
+ I2 = 2,
+ I3 = 3,
+ I4 = 4,
+ I5 = 5,
+ I6 = 6,
+ I7 = 7,
+ I8 = 8,
+ I9 = 9,
+ I10 = 10,
+ I11 = 11,
+ I12 = 12,
+ I13 = 13,
+ I14 = 14,
+ I15 = 15,
+ D0 = 16,
+ D1 = 17,
+ D2 = 18,
+ D3 = 19,
+ D4 = 20,
+ D5 = 21,
+ D6 = 22,
+ D7 = 23,
+ D8 = 24,
+ D9 = 25,
+ D10 = 26,
+ D11 = 27,
+ D12 = 28,
+ D13 = 29,
+ D14 = 30,
+ D15 = 31,
+};
+
+namespace lp64 {
+struct LastThreadContext {
+ u64 fp;
+ u64 sp;
+ u64 lr;
+ u64 pc;
+};
+
+struct PhysicalMemoryInfo {
+ PAddr physical_address;
+ u64 virtual_address;
+ u64 size;
+};
+
+struct DebugInfoCreateProcess {
+ u64 program_id;
+ u64 process_id;
+ std::array<char, 0xC> name;
+ u32 flags;
+ u64 user_exception_context_address; // 5.0.0+
+};
+
+struct DebugInfoCreateThread {
+ u64 thread_id;
+ u64 tls_address;
+ // Removed in 11.0.0 u64 entrypoint;
+};
+
+struct DebugInfoExitProcess {
+ ProcessExitReason reason;
+};
+
+struct DebugInfoExitThread {
+ ThreadExitReason reason;
+};
+
+struct DebugInfoUndefinedInstructionException {
+ u32 insn;
+};
+
+struct DebugInfoDataAbortException {
+ u64 address;
+};
+
+struct DebugInfoAlignmentFaultException {
+ u64 address;
+};
+
+struct DebugInfoBreakPointException {
+ BreakPointType type;
+ u64 address;
+};
+
+struct DebugInfoUserBreakException {
+ BreakReason break_reason;
+ u64 address;
+ u64 size;
+};
+
+struct DebugInfoDebuggerBreakException {
+ std::array<u64, 4> active_thread_ids;
+};
+
+struct DebugInfoUndefinedSystemCallException {
+ u32 id;
+};
+
+union DebugInfoSpecificException {
+ DebugInfoUndefinedInstructionException undefined_instruction;
+ DebugInfoDataAbortException data_abort;
+ DebugInfoAlignmentFaultException alignment_fault;
+ DebugInfoBreakPointException break_point;
+ DebugInfoUserBreakException user_break;
+ DebugInfoDebuggerBreakException debugger_break;
+ DebugInfoUndefinedSystemCallException undefined_system_call;
+ u64 raw;
+};
+
+struct DebugInfoException {
+ DebugException type;
+ u64 address;
+ DebugInfoSpecificException specific;
+};
+
+union DebugInfo {
+ DebugInfoCreateProcess create_process;
+ DebugInfoCreateThread create_thread;
+ DebugInfoExitProcess exit_process;
+ DebugInfoExitThread exit_thread;
+ DebugInfoException exception;
+};
+
+struct DebugEventInfo {
+ DebugEvent type;
+ u32 flags;
+ u64 thread_id;
+ DebugInfo info;
+};
+static_assert(sizeof(DebugEventInfo) >= 0x40);
+
+struct SecureMonitorArguments {
+ std::array<u64, 8> r;
+};
+static_assert(sizeof(SecureMonitorArguments) == 0x40);
+} // namespace lp64
+
+namespace ilp32 {
+struct LastThreadContext {
+ u32 fp;
+ u32 sp;
+ u32 lr;
+ u32 pc;
+};
+
+struct PhysicalMemoryInfo {
+ PAddr physical_address;
+ u32 virtual_address;
+ u32 size;
+};
+
+struct DebugInfoCreateProcess {
+ u64 program_id;
+ u64 process_id;
+ std::array<char, 0xC> name;
+ u32 flags;
+ u32 user_exception_context_address; // 5.0.0+
+};
+
+struct DebugInfoCreateThread {
+ u64 thread_id;
+ u32 tls_address;
+ // Removed in 11.0.0 u32 entrypoint;
+};
+
+struct DebugInfoExitProcess {
+ ProcessExitReason reason;
+};
+
+struct DebugInfoExitThread {
+ ThreadExitReason reason;
+};
+
+struct DebugInfoUndefinedInstructionException {
+ u32 insn;
+};
+
+struct DebugInfoDataAbortException {
+ u32 address;
+};
+
+struct DebugInfoAlignmentFaultException {
+ u32 address;
+};
+
+struct DebugInfoBreakPointException {
+ BreakPointType type;
+ u32 address;
+};
+
+struct DebugInfoUserBreakException {
+ BreakReason break_reason;
+ u32 address;
+ u32 size;
+};
+
+struct DebugInfoDebuggerBreakException {
+ std::array<u64, 4> active_thread_ids;
+};
+
+struct DebugInfoUndefinedSystemCallException {
+ u32 id;
+};
+
+union DebugInfoSpecificException {
+ DebugInfoUndefinedInstructionException undefined_instruction;
+ DebugInfoDataAbortException data_abort;
+ DebugInfoAlignmentFaultException alignment_fault;
+ DebugInfoBreakPointException break_point;
+ DebugInfoUserBreakException user_break;
+ DebugInfoDebuggerBreakException debugger_break;
+ DebugInfoUndefinedSystemCallException undefined_system_call;
+ u64 raw;
+};
+
+struct DebugInfoException {
+ DebugException type;
+ u32 address;
+ DebugInfoSpecificException specific;
+};
+
+union DebugInfo {
+ DebugInfoCreateProcess create_process;
+ DebugInfoCreateThread create_thread;
+ DebugInfoExitProcess exit_process;
+ DebugInfoExitThread exit_thread;
+ DebugInfoException exception;
+};
+
+struct DebugEventInfo {
+ DebugEvent type;
+ u32 flags;
+ u64 thread_id;
+ DebugInfo info;
+};
+
+struct SecureMonitorArguments {
+ std::array<u32, 8> r;
+};
+static_assert(sizeof(SecureMonitorArguments) == 0x20);
+} // namespace ilp32
+
+struct ThreadContext {
+ std::array<u64, 29> r;
+ u64 fp;
+ u64 lr;
+ u64 sp;
+ u64 pc;
+ u32 pstate;
+ u32 padding;
+ std::array<u128, 32> v;
+ u32 fpcr;
+ u32 fpsr;
+ u64 tpidr;
+};
+static_assert(sizeof(ThreadContext) == 0x320);
+
+struct MemoryInfo {
+ u64 base_address;
+ u64 size;
+ MemoryState state;
+ MemoryAttribute attribute;
+ MemoryPermission permission;
+ u32 ipc_count;
+ u32 device_count;
+ u32 padding;
+};
+
+enum class LimitableResource : u32 {
+ PhysicalMemoryMax = 0,
+ ThreadCountMax = 1,
+ EventCountMax = 2,
+ TransferMemoryCountMax = 3,
+ SessionCountMax = 4,
+ Count,
+};
+
+enum class IoPoolType : u32 {
+ // Not supported.
+ Count = 0,
+};
+
+enum class MemoryMapping : u32 {
+ IoRegister = 0,
+ Uncached = 1,
+ Memory = 2,
+};
+
+enum class KernelDebugType : u32 {
+ Thread = 0,
+ ThreadCallStack = 1,
+ KernelObject = 2,
+ Handle_ = 3,
+ Memory = 4,
+ PageTable = 5,
+ CpuUtilization = 6,
+ Process = 7,
+ SuspendProcess = 8,
+ ResumeProcess = 9,
+ Port = 10,
+};
+
+enum class KernelTraceState : u32 {
+ Disabled = 0,
+ Enabled = 1,
+};
+
+enum class CodeMemoryOperation : u32 {
+ Map = 0,
+ MapToOwner = 1,
+ Unmap = 2,
+ UnmapFromOwner = 3,
+};
+
+enum class InterruptType : u32 {
+ Edge = 0,
+ Level = 1,
+};
+
+enum class DeviceName {
+ Afi = 0,
+ Avpc = 1,
+ Dc = 2,
+ Dcb = 3,
+ Hc = 4,
+ Hda = 5,
+ Isp2 = 6,
+ MsencNvenc = 7,
+ Nv = 8,
+ Nv2 = 9,
+ Ppcs = 10,
+ Sata = 11,
+ Vi = 12,
+ Vic = 13,
+ XusbHost = 14,
+ XusbDev = 15,
+ Tsec = 16,
+ Ppcs1 = 17,
+ Dc1 = 18,
+ Sdmmc1a = 19,
+ Sdmmc2a = 20,
+ Sdmmc3a = 21,
+ Sdmmc4a = 22,
+ Isp2b = 23,
+ Gpu = 24,
+ Gpub = 25,
+ Ppcs2 = 26,
+ Nvdec = 27,
+ Ape = 28,
+ Se = 29,
+ Nvjpg = 30,
+ Hc1 = 31,
+ Se1 = 32,
+ Axiap = 33,
+ Etr = 34,
+ Tsecb = 35,
+ Tsec1 = 36,
+ Tsecb1 = 37,
+ Nvdec1 = 38,
+ Count,
+};
+
+enum class SystemInfoType : u32 {
+ TotalPhysicalMemorySize = 0,
+ UsedPhysicalMemorySize = 1,
+ InitialProcessIdRange = 2,
+};
+
+enum class ProcessInfoType : u32 {
+ ProcessState = 0,
+};
+
+struct CreateProcessParameter {
+ std::array<char, 12> name;
+ u32 version;
+ u64 program_id;
+ u64 code_address;
+ s32 code_num_pages;
+ u32 flags;
+ Handle reslimit;
+ s32 system_resource_num_pages;
+};
+static_assert(sizeof(CreateProcessParameter) == 0x30);
+
} // namespace Kernel::Svc
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index ef4b2d417..56c990728 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -423,16 +423,17 @@ constexpr void UpdateCurrentResultReference<const Result>(Result result_referenc
} // namespace ResultImpl
#define DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(COUNTER_VALUE) \
- [[maybe_unused]] constexpr bool HasPrevRef_##COUNTER_VALUE = \
+ [[maybe_unused]] constexpr bool CONCAT2(HasPrevRef_, COUNTER_VALUE) = \
std::same_as<decltype(__TmpCurrentResultReference), Result&>; \
- [[maybe_unused]] auto& PrevRef_##COUNTER_VALUE = __TmpCurrentResultReference; \
- [[maybe_unused]] Result __tmp_result_##COUNTER_VALUE = ResultSuccess; \
- Result& __TmpCurrentResultReference = \
- HasPrevRef_##COUNTER_VALUE ? PrevRef_##COUNTER_VALUE : __tmp_result_##COUNTER_VALUE
+ [[maybe_unused]] Result CONCAT2(PrevRef_, COUNTER_VALUE) = __TmpCurrentResultReference; \
+ [[maybe_unused]] Result CONCAT2(__tmp_result_, COUNTER_VALUE) = ResultSuccess; \
+ Result& __TmpCurrentResultReference = CONCAT2(HasPrevRef_, COUNTER_VALUE) \
+ ? CONCAT2(PrevRef_, COUNTER_VALUE) \
+ : CONCAT2(__tmp_result_, COUNTER_VALUE)
#define ON_RESULT_RETURN_IMPL(...) \
static_assert(std::same_as<decltype(__TmpCurrentResultReference), Result&>); \
- auto RESULT_GUARD_STATE_##__COUNTER__ = \
+ auto CONCAT2(RESULT_GUARD_STATE_, __COUNTER__) = \
ResultImpl::ResultReferenceForScopedResultGuard<__VA_ARGS__>( \
__TmpCurrentResultReference) + \
[&]()
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index bb838e285..85a3f0802 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -512,10 +512,11 @@ protected:
class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
public:
- explicit IManagerForApplication(Core::System& system_, Common::UUID user_id_)
+ explicit IManagerForApplication(Core::System& system_,
+ const std::shared_ptr<ProfileManager>& profile_manager_)
: ServiceFramework{system_, "IManagerForApplication"},
ensure_token_id{std::make_shared<EnsureTokenIdCacheAsyncInterface>(system)},
- user_id{user_id_} {
+ profile_manager{profile_manager_} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IManagerForApplication::CheckAvailability, "CheckAvailability"},
@@ -545,7 +546,7 @@ private:
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.PushRaw<u64>(user_id.Hash());
+ rb.PushRaw<u64>(profile_manager->GetLastOpenedUser().Hash());
}
void EnsureIdTokenCacheAsync(Kernel::HLERequestContext& ctx) {
@@ -575,17 +576,20 @@ private:
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.PushRaw<u64>(user_id.Hash());
+ rb.PushRaw<u64>(profile_manager->GetLastOpenedUser().Hash());
}
void StoreOpenContext(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
+ LOG_DEBUG(Service_ACC, "called");
+
+ profile_manager->StoreOpenedUsers();
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
std::shared_ptr<EnsureTokenIdCacheAsyncInterface> ensure_token_id{};
- Common::UUID user_id{};
+ std::shared_ptr<ProfileManager> profile_manager;
};
// 6.0.0+
@@ -790,7 +794,7 @@ void Module::Interface::GetBaasAccountManagerForApplication(Kernel::HLERequestCo
LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IManagerForApplication>(system, profile_manager->GetLastOpenedUser());
+ rb.PushIpcInterface<IManagerForApplication>(system, profile_manager);
}
void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx) {
@@ -849,22 +853,10 @@ void Module::Interface::ListQualifiedUsers(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
-void Module::Interface::LoadOpenContext(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
-
- // This is similar to GetBaasAccountManagerForApplication
- // This command is used concurrently with ListOpenContextStoredUsers
- // TODO: Find the differences between this and GetBaasAccountManagerForApplication
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IManagerForApplication>(system, profile_manager->GetLastOpenedUser());
-}
-
void Module::Interface::ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
+ LOG_DEBUG(Service_ACC, "called");
- // TODO(ogniK): Handle open contexts
- ctx.WriteBuffer(profile_manager->GetOpenUsers());
+ ctx.WriteBuffer(profile_manager->GetStoredOpenedUsers());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index 1621e7c0a..9411b0b92 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -35,7 +35,6 @@ public:
void InitializeApplicationInfoV2(Kernel::HLERequestContext& ctx);
void GetProfileEditor(Kernel::HLERequestContext& ctx);
void ListQualifiedUsers(Kernel::HLERequestContext& ctx);
- void LoadOpenContext(Kernel::HLERequestContext& ctx);
void ListOpenContextStoredUsers(Kernel::HLERequestContext& ctx);
void StoreSaveDataThumbnailApplication(Kernel::HLERequestContext& ctx);
void StoreSaveDataThumbnailSystem(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp
index 65023b8c2..54844bfe7 100644
--- a/src/core/hle/service/acc/acc_u0.cpp
+++ b/src/core/hle/service/acc/acc_u0.cpp
@@ -28,7 +28,7 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module_, std::shared_ptr<ProfileManager>
{110, &ACC_U0::StoreSaveDataThumbnailApplication, "StoreSaveDataThumbnail"},
{111, nullptr, "ClearSaveDataThumbnail"},
{120, nullptr, "CreateGuestLoginRequest"},
- {130, &ACC_U0::LoadOpenContext, "LoadOpenContext"}, // 5.0.0+
+ {130, nullptr, "LoadOpenContext"}, // 5.0.0+
{131, &ACC_U0::ListOpenContextStoredUsers, "ListOpenContextStoredUsers"}, // 6.0.0+
{140, &ACC_U0::InitializeApplicationInfoRestricted, "InitializeApplicationInfoRestricted"}, // 6.0.0+
{141, &ACC_U0::ListQualifiedUsers, "ListQualifiedUsers"}, // 6.0.0+
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index a58da4d5f..481e0d141 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -261,6 +261,31 @@ UUID ProfileManager::GetLastOpenedUser() const {
return last_opened_user;
}
+/// Gets the list of stored opened users.
+UserIDArray ProfileManager::GetStoredOpenedUsers() const {
+ UserIDArray output{};
+ std::ranges::transform(stored_opened_profiles, output.begin(), [](const ProfileInfo& p) {
+ if (p.is_open)
+ return p.user_uuid;
+ return Common::InvalidUUID;
+ });
+ std::stable_partition(output.begin(), output.end(),
+ [](const UUID& uuid) { return uuid.IsValid(); });
+ return output;
+}
+
+/// Captures the opened users, which can be queried across process launches with
+/// ListOpenContextStoredUsers.
+void ProfileManager::StoreOpenedUsers() {
+ size_t profile_index{};
+ stored_opened_profiles = {};
+ std::for_each(profiles.begin(), profiles.end(), [&](const auto& profile) {
+ if (profile.is_open) {
+ stored_opened_profiles[profile_index++] = profile;
+ }
+ });
+}
+
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
UserData& data) const {
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index 135f7d0d5..993a5a57a 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -86,6 +86,8 @@ public:
UserIDArray GetOpenUsers() const;
UserIDArray GetAllUsers() const;
Common::UUID GetLastOpenedUser() const;
+ UserIDArray GetStoredOpenedUsers() const;
+ void StoreOpenedUsers();
bool CanSystemRegisterUser() const;
@@ -101,6 +103,7 @@ private:
bool RemoveProfileAtIndex(std::size_t index);
std::array<ProfileInfo, MAX_USERS> profiles{};
+ std::array<ProfileInfo, MAX_USERS> stored_opened_profiles{};
std::size_t user_count{};
Common::UUID last_opened_user{};
};
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index e55233054..8ea7fd760 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -299,7 +299,7 @@ ISelfController::ISelfController(Core::System& system_, NVFlinger::NVFlinger& nv
{100, &ISelfController::SetAlbumImageTakenNotificationEnabled, "SetAlbumImageTakenNotificationEnabled"},
{110, nullptr, "SetApplicationAlbumUserData"},
{120, &ISelfController::SaveCurrentScreenshot, "SaveCurrentScreenshot"},
- {130, nullptr, "SetRecordVolumeMuted"},
+ {130, &ISelfController::SetRecordVolumeMuted, "SetRecordVolumeMuted"},
{1000, nullptr, "GetDebugStorageChannel"},
};
// clang-format on
@@ -597,6 +597,17 @@ void ISelfController::SaveCurrentScreenshot(Kernel::HLERequestContext& ctx) {
rb.Push(ResultSuccess);
}
+void ISelfController::SetRecordVolumeMuted(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+
+ const auto is_record_volume_muted = rp.Pop<bool>();
+
+ LOG_WARNING(Service_AM, "(STUBBED) called. is_record_volume_muted={}", is_record_volume_muted);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
AppletMessageQueue::AppletMessageQueue(Core::System& system)
: service_context{system, "AppletMessageQueue"} {
on_new_message = service_context.CreateEvent("AMMessageQueue:OnMessageReceived");
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index bb75c6281..a0fbfcfc5 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -182,6 +182,7 @@ private:
void GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequestContext& ctx);
void SetAlbumImageTakenNotificationEnabled(Kernel::HLERequestContext& ctx);
void SaveCurrentScreenshot(Kernel::HLERequestContext& ctx);
+ void SetRecordVolumeMuted(Kernel::HLERequestContext& ctx);
enum class ScreenshotPermission : u32 {
Inherit = 0,
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
index 48a9a73a0..608925dfc 100644
--- a/src/core/hle/service/audio/audin_u.cpp
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -17,7 +17,7 @@ using namespace AudioCore::AudioIn;
class IAudioIn final : public ServiceFramework<IAudioIn> {
public:
explicit IAudioIn(Core::System& system_, Manager& manager, size_t session_id,
- std::string& device_name, const AudioInParameter& in_params, u32 handle,
+ const std::string& device_name, const AudioInParameter& in_params, u32 handle,
u64 applet_resource_user_id)
: ServiceFramework{system_, "IAudioIn"},
service_context{system_, "IAudioIn"}, event{service_context.CreateEvent("AudioInEvent")},
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index 49c092301..122290c6a 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -24,7 +24,7 @@ using namespace AudioCore::AudioOut;
class IAudioOut final : public ServiceFramework<IAudioOut> {
public:
explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager,
- size_t session_id, std::string& device_name,
+ size_t session_id, const std::string& device_name,
const AudioOutParameter& in_params, u32 handle, u64 applet_resource_user_id)
: ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew},
service_context{system_, "IAudioOut"}, event{service_context.CreateEvent(
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 3b26e96de..2f871de31 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -868,7 +868,7 @@ bool Controller_NPad::VibrateControllerAtIndex(Core::HID::NpadIdType npad_id,
return false;
}
- if (!controller.device->IsVibrationEnabled()) {
+ if (!controller.device->IsVibrationEnabled(device_index)) {
if (controller.vibration[device_index].latest_vibration_value.low_amplitude != 0.0f ||
controller.vibration[device_index].latest_vibration_value.high_amplitude != 0.0f) {
// Send an empty vibration to stop any vibrations.
@@ -1001,7 +1001,7 @@ void Controller_NPad::InitializeVibrationDeviceAtIndex(Core::HID::NpadIdType npa
}
controller.vibration[device_index].device_mounted =
- controller.device->TestVibration(device_index);
+ controller.device->IsVibrationEnabled(device_index);
}
void Controller_NPad::SetPermitVibrationSession(bool permit_vibration_session) {
diff --git a/src/core/hle/service/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp
index af133af93..42991928e 100644
--- a/src/core/hle/service/kernel_helpers.cpp
+++ b/src/core/hle/service/kernel_helpers.cpp
@@ -31,7 +31,7 @@ ServiceContext::~ServiceContext() {
Kernel::KEvent* ServiceContext::CreateEvent(std::string&& name) {
// Reserve a new event from the process resource limit
Kernel::KScopedResourceReservation event_reservation(process,
- Kernel::LimitableResource::Events);
+ Kernel::LimitableResource::EventCountMax);
if (!event_reservation.Succeeded()) {
LOG_CRITICAL(Service, "Resource limit reached!");
return {};
diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp
index c32a6816b..167e29572 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.cpp
+++ b/src/core/hle/service/nfp/amiibo_crypto.cpp
@@ -9,6 +9,7 @@
#include <mbedtls/hmac_drbg.h>
#include "common/fs/file.h"
+#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "core/hle/service/mii/mii_manager.h"
@@ -279,7 +280,7 @@ bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
Common::FS::FileType::BinaryFile};
if (!keys_file.IsOpen()) {
- LOG_ERROR(Service_NFP, "No keys detected");
+ LOG_ERROR(Service_NFP, "Failed to open key file");
return false;
}
@@ -295,6 +296,11 @@ bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
return true;
}
+bool IsKeyAvailable() {
+ const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
+ return Common::FS::Exists(yuzu_keys_dir / "key_retail.bin");
+}
+
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) {
InternalKey locked_secret{};
InternalKey unfixed_info{};
diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h
index 0175ced91..1fa61174e 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.h
+++ b/src/core/hle/service/nfp/amiibo_crypto.h
@@ -91,6 +91,9 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou
/// Loads both amiibo keys from key_retail.bin
bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info);
+/// Returns true if key_retail.bin exist
+bool IsKeyAvailable();
+
/// Decodes encripted amiibo data returns true if output is valid
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data);
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
index 76f8a267a..b19672560 100644
--- a/src/core/hle/service/nfp/nfp_device.cpp
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -17,6 +17,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/mii/types.h"
#include "core/hle/service/nfp/amiibo_crypto.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_device.h"
@@ -233,6 +234,14 @@ Result NfpDevice::Mount(MountTarget mount_target_) {
return NotAnAmiibo;
}
+ // Mark amiibos as read only when keys are missing
+ if (!AmiiboCrypto::IsKeyAvailable()) {
+ LOG_ERROR(Service_NFP, "No keys detected");
+ device_state = DeviceState::TagMounted;
+ mount_target = MountTarget::Rom;
+ return ResultSuccess;
+ }
+
if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
return CorruptedData;
diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h
index a5b72cf19..76d0e9ae4 100644
--- a/src/core/hle/service/nfp/nfp_device.h
+++ b/src/core/hle/service/nfp/nfp_device.h
@@ -8,7 +8,6 @@
#include "common/common_funcs.h"
#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/mii/types.h"
#include "core/hle/service/nfp/nfp_types.h"
#include "core/hle/service/service.h"
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
index c09f9ddb6..63d5917cb 100644
--- a/src/core/hle/service/nfp/nfp_types.h
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -17,11 +17,6 @@ enum class ServiceType : u32 {
System,
};
-enum class State : u32 {
- NonInitialized,
- Initialized,
-};
-
enum class DeviceState : u32 {
Initialized,
SearchingForTag,
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
index 4ed53b534..33e2ef518 100644
--- a/src/core/hle/service/nfp/nfp_user.cpp
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -6,12 +6,9 @@
#include "common/logging/log.h"
#include "core/core.h"
-#include "core/hid/emulated_controller.h"
-#include "core/hid/hid_core.h"
#include "core/hid/hid_types.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
-#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/nfp/nfp_device.h"
#include "core/hle/service/nfp/nfp_result.h"
#include "core/hle/service/nfp/nfp_user.h"
diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h
index 68c60ae82..47aff3695 100644
--- a/src/core/hle/service/nfp/nfp_user.h
+++ b/src/core/hle/service/nfp/nfp_user.h
@@ -4,8 +4,7 @@
#pragma once
#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/nfp/nfp.h"
-#include "core/hle/service/nfp/nfp_types.h"
+#include "core/hle/service/service.h"
namespace Service::NFP {
class NfpDevice;
@@ -15,6 +14,11 @@ public:
explicit IUser(Core::System& system_);
private:
+ enum class State : u32 {
+ NonInitialized,
+ Initialized,
+ };
+
void Initialize(Kernel::HLERequestContext& ctx);
void Finalize(Kernel::HLERequestContext& ctx);
void ListDevices(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp
index fbd8a74a5..a51ca5444 100644
--- a/src/core/hle/service/nvdrv/core/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/core/nvmap.cpp
@@ -255,15 +255,16 @@ std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool interna
.address = handle_description->address,
.size = handle_description->size,
.was_uncached = handle_description->flags.map_uncached.Value() != 0,
+ .can_unlock = true,
};
} else {
return std::nullopt;
}
- // Handle hasn't been freed from memory, set address to 0 to mark that the handle wasn't freed
+ // If the handle hasn't been freed from memory, mark that
if (!hWeak.expired()) {
LOG_DEBUG(Service_NVDRV, "nvmap handle: {} wasn't freed as it is still in use", handle);
- freeInfo.address = 0;
+ freeInfo.can_unlock = false;
}
return freeInfo;
diff --git a/src/core/hle/service/nvdrv/core/nvmap.h b/src/core/hle/service/nvdrv/core/nvmap.h
index b9dd3801f..a8e573890 100644
--- a/src/core/hle/service/nvdrv/core/nvmap.h
+++ b/src/core/hle/service/nvdrv/core/nvmap.h
@@ -105,6 +105,7 @@ public:
u64 address; //!< Address the handle referred to before deletion
u64 size; //!< Page-aligned handle size
bool was_uncached; //!< If the handle was allocated as uncached
+ bool can_unlock; //!< If the address region is ready to be unlocked
};
explicit NvMap(Tegra::Host1x::Host1x& host1x);
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index b60679021..fa29db758 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -126,10 +126,12 @@ NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output)
LOG_CRITICAL(Service_NVDRV, "Object failed to allocate, handle={:08X}", params.handle);
return result;
}
+ bool is_out_io{};
ASSERT(system.CurrentProcess()
->PageTable()
- .LockForMapDeviceAddressSpace(handle_description->address, handle_description->size,
- Kernel::KMemoryPermission::None, true)
+ .LockForMapDeviceAddressSpace(&is_out_io, handle_description->address,
+ handle_description->size,
+ Kernel::KMemoryPermission::None, true, false)
.IsSuccess());
std::memcpy(output.data(), &params, sizeof(params));
return result;
@@ -251,10 +253,12 @@ NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
}
if (auto freeInfo{file.FreeHandle(params.handle, false)}) {
- ASSERT(system.CurrentProcess()
- ->PageTable()
- .UnlockForDeviceAddressSpace(freeInfo->address, freeInfo->size)
- .IsSuccess());
+ if (freeInfo->can_unlock) {
+ ASSERT(system.CurrentProcess()
+ ->PageTable()
+ .UnlockForDeviceAddressSpace(freeInfo->address, freeInfo->size)
+ .IsSuccess());
+ }
params.address = freeInfo->address;
params.size = static_cast<u32>(freeInfo->size);
params.flags.raw = 0;
diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp
index 77ddbb6ef..41ba44b21 100644
--- a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp
@@ -742,6 +742,13 @@ Status BufferQueueProducer::Disconnect(NativeWindowApi api) {
return Status::NoError;
}
+ // HACK: We are not Android. Remove handle for items in queue, and clear queue.
+ // Allows synchronous destruction of nvmap handles.
+ for (auto& item : core->queue) {
+ nvmap.FreeHandle(item.graphic_buffer->BufferId(), true);
+ }
+ core->queue.clear();
+
switch (api) {
case NativeWindowApi::Egl:
case NativeWindowApi::Cpu:
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index dad93b38e..c3af12c90 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -138,6 +138,19 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
return itr->GetID();
}
+bool NVFlinger::CloseDisplay(u64 display_id) {
+ const auto lock_guard = Lock();
+ auto* const display = FindDisplay(display_id);
+
+ if (display == nullptr) {
+ return false;
+ }
+
+ display->Reset();
+
+ return true;
+}
+
std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
const auto lock_guard = Lock();
auto* const display = FindDisplay(display_id);
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index b8191c595..460bef976 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -58,6 +58,11 @@ public:
/// If an invalid display name is provided, then an empty optional is returned.
[[nodiscard]] std::optional<u64> OpenDisplay(std::string_view name);
+ /// Closes the specified display by its ID.
+ ///
+ /// Returns false if an invalid display ID is provided.
+ [[nodiscard]] bool CloseDisplay(u64 display_id);
+
/// Creates a layer on the specified display and returns the layer ID.
///
/// If an invalid display ID is specified, then an empty optional is returned.
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 5db6588e4..5ab41c0c4 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -99,6 +99,12 @@ ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* se
ServiceFrameworkBase::~ServiceFrameworkBase() {
// Wait for other threads to release access before destroying
const auto guard = LockService();
+
+ if (named_port != nullptr) {
+ named_port->GetClientPort().Close();
+ named_port->GetServerPort().Close();
+ named_port = nullptr;
+ }
}
void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) {
@@ -113,15 +119,16 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager)
Kernel::KClientPort& ServiceFrameworkBase::CreatePort() {
const auto guard = LockService();
- ASSERT(!service_registered);
+ if (named_port == nullptr) {
+ ASSERT(!service_registered);
- auto* port = Kernel::KPort::Create(kernel);
- port->Initialize(max_sessions, false, service_name);
- port->GetServerPort().SetSessionHandler(shared_from_this());
+ named_port = Kernel::KPort::Create(kernel);
+ named_port->Initialize(max_sessions, false, service_name);
- service_registered = true;
+ service_registered = true;
+ }
- return port->GetClientPort();
+ return named_port->GetClientPort();
}
void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n) {
@@ -199,7 +206,6 @@ Result ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& session,
switch (ctx.GetCommandType()) {
case IPC::CommandType::Close:
case IPC::CommandType::TIPC_Close: {
- session.Close();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
result = IPC::ERR_REMOTE_PROCESS_DEAD;
@@ -244,6 +250,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false);
system.Kernel().RegisterNamedService("sm:", SM::ServiceManager::InterfaceFactory);
+ system.Kernel().RegisterInterfaceForNamedService("sm:", SM::ServiceManager::SessionHandler);
Account::InstallInterfaces(system);
AM::InstallInterfaces(*sm, *nv_flinger, system);
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index ec9deeee4..22e2119d7 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -20,6 +20,7 @@ class System;
namespace Kernel {
class HLERequestContext;
class KClientPort;
+class KPort;
class KServerSession;
class ServiceThread;
} // namespace Kernel
@@ -98,6 +99,9 @@ protected:
/// Identifier string used to connect to the service.
std::string service_name;
+ /// Port used by ManageNamedPort.
+ Kernel::KPort* named_port{};
+
private:
template <typename T>
friend class ServiceFramework;
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index 48e70f93c..84720094f 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -23,7 +23,13 @@ constexpr Result ERR_INVALID_NAME(ErrorModule::SM, 6);
constexpr Result ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7);
ServiceManager::ServiceManager(Kernel::KernelCore& kernel_) : kernel{kernel_} {}
-ServiceManager::~ServiceManager() = default;
+
+ServiceManager::~ServiceManager() {
+ for (auto& [name, port] : service_ports) {
+ port->GetClientPort().Close();
+ port->GetServerPort().Close();
+ }
+}
void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
controller_interface->InvokeRequest(context);
@@ -43,6 +49,10 @@ Kernel::KClientPort& ServiceManager::InterfaceFactory(ServiceManager& self, Core
return self.sm_interface->CreatePort();
}
+void ServiceManager::SessionHandler(ServiceManager& self, Kernel::KServerPort* server_port) {
+ self.sm_interface->AcceptSession(server_port);
+}
+
Result ServiceManager::RegisterService(std::string name, u32 max_sessions,
Kernel::SessionRequestHandlerPtr handler) {
@@ -53,7 +63,11 @@ Result ServiceManager::RegisterService(std::string name, u32 max_sessions,
return ERR_ALREADY_REGISTERED;
}
- registered_services.emplace(std::move(name), handler);
+ auto* port = Kernel::KPort::Create(kernel);
+ port->Initialize(ServerSessionCountMax, false, name);
+
+ service_ports.emplace(name, port);
+ registered_services.emplace(name, handler);
return ResultSuccess;
}
@@ -68,25 +82,20 @@ Result ServiceManager::UnregisterService(const std::string& name) {
}
registered_services.erase(iter);
+ service_ports.erase(name);
+
return ResultSuccess;
}
ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name) {
CASCADE_CODE(ValidateServiceName(name));
- auto it = registered_services.find(name);
- if (it == registered_services.end()) {
+ auto it = service_ports.find(name);
+ if (it == service_ports.end()) {
LOG_ERROR(Service_SM, "Server is not registered! service={}", name);
return ERR_SERVICE_NOT_REGISTERED;
}
- auto* port = Kernel::KPort::Create(kernel);
- SCOPE_EXIT({ port->Close(); });
-
- port->Initialize(ServerSessionCountMax, false, name);
- auto handler = it->second;
- port->GetServerPort().SetSessionHandler(std::move(handler));
-
- return port;
+ return it->second;
}
/**
@@ -145,23 +154,20 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(Kernel::HLERequestContext&
// Find the named port.
auto port_result = service_manager.GetServicePort(name);
- if (port_result.Failed()) {
+ auto service = service_manager.GetService<Kernel::SessionRequestHandler>(name);
+ if (port_result.Failed() || !service) {
LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, port_result.Code().raw);
return port_result.Code();
}
auto& port = port_result.Unwrap();
- SCOPE_EXIT({ port->GetClientPort().Close(); });
-
- kernel.RegisterServerObject(&port->GetServerPort());
// Create a new session.
Kernel::KClientSession* session{};
- if (const auto result = port->GetClientPort().CreateSession(
- std::addressof(session), std::make_shared<Kernel::SessionRequestManager>(kernel));
- result.IsError()) {
+ if (const auto result = port->GetClientPort().CreateSession(&session); result.IsError()) {
LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.raw);
return result;
}
+ service->AcceptSession(&port->GetServerPort());
LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId());
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index 878decc6f..02a5dde9e 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -51,6 +51,7 @@ private:
class ServiceManager {
public:
static Kernel::KClientPort& InterfaceFactory(ServiceManager& self, Core::System& system);
+ static void SessionHandler(ServiceManager& self, Kernel::KServerPort* server_port);
explicit ServiceManager(Kernel::KernelCore& kernel_);
~ServiceManager();
@@ -78,6 +79,7 @@ private:
/// Map of registered services, retrieved using GetServicePort.
std::unordered_map<std::string, Kernel::SessionRequestHandlerPtr> registered_services;
+ std::unordered_map<std::string, Kernel::KPort*> service_ports;
/// Kernel context
Kernel::KernelCore& kernel;
diff --git a/src/core/hle/service/sm/sm_controller.cpp b/src/core/hle/service/sm/sm_controller.cpp
index 273f79568..1cf9dd1c4 100644
--- a/src/core/hle/service/sm/sm_controller.cpp
+++ b/src/core/hle/service/sm/sm_controller.cpp
@@ -15,10 +15,9 @@
namespace Service::SM {
void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) {
- ASSERT_MSG(!ctx.Session()->GetSessionRequestManager()->IsDomain(),
- "Session is already a domain");
+ ASSERT_MSG(!ctx.GetManager()->IsDomain(), "Session is already a domain");
LOG_DEBUG(Service, "called, server_session={}", ctx.Session()->GetId());
- ctx.Session()->GetSessionRequestManager()->ConvertToDomainOnRequestEnd();
+ ctx.GetManager()->ConvertToDomainOnRequestEnd();
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@@ -28,23 +27,35 @@ void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) {
void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called");
- auto& parent_session = *ctx.Session()->GetParent();
- auto& parent_port = parent_session.GetParent()->GetParent()->GetClientPort();
- auto& session_manager = parent_session.GetServerSession().GetSessionRequestManager();
+ auto& process = *ctx.GetThread().GetOwnerProcess();
+ auto session_manager = ctx.GetManager();
- // Create a session.
- Kernel::KClientSession* session{};
- const Result result = parent_port.CreateSession(std::addressof(session), session_manager);
- if (result.IsError()) {
- LOG_CRITICAL(Service, "CreateSession failed with error 0x{:08X}", result.raw);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- }
+ // FIXME: this is duplicated from the SVC, it should just call it instead
+ // once this is a proper process
+
+ // Reserve a new session from the process resource limit.
+ Kernel::KScopedResourceReservation session_reservation(
+ &process, Kernel::LimitableResource::SessionCountMax);
+ ASSERT(session_reservation.Succeeded());
+
+ // Create the session.
+ Kernel::KSession* session = Kernel::KSession::Create(system.Kernel());
+ ASSERT(session != nullptr);
+
+ // Initialize the session.
+ session->Initialize(nullptr, "");
+
+ // Commit the session reservation.
+ session_reservation.Commit();
+
+ // Register with manager.
+ session_manager->SessionHandler().RegisterSession(&session->GetServerSession(),
+ session_manager);
// We succeeded.
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
rb.Push(ResultSuccess);
- rb.PushMoveObjects(session);
+ rb.PushMoveObjects(session->GetClientSession());
}
void Controller::CloneCurrentObjectEx(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h
index 33d5f398c..0b65a65da 100644
--- a/src/core/hle/service/vi/display/vi_display.h
+++ b/src/core/hle/service/vi/display/vi_display.h
@@ -106,6 +106,12 @@ public:
///
void CloseLayer(u64 layer_id);
+ /// Resets the display for a new connection.
+ void Reset() {
+ layers.clear();
+ got_vsync_event = false;
+ }
+
/// Attempts to find a layer with the given ID.
///
/// @param layer_id The layer ID.
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 9c917cacf..bb283e74e 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -324,10 +324,10 @@ private:
IPC::RequestParser rp{ctx};
const u64 display = rp.Pop<u64>();
- LOG_WARNING(Service_VI, "(STUBBED) called. display=0x{:016X}", display);
+ const Result rc = nv_flinger.CloseDisplay(display) ? ResultSuccess : ResultUnknown;
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(rc);
}
void CreateManagedLayer(Kernel::HLERequestContext& ctx) {
@@ -508,10 +508,10 @@ private:
IPC::RequestParser rp{ctx};
const u64 display_id = rp.Pop<u64>();
- LOG_WARNING(Service_VI, "(STUBBED) called. display_id=0x{:016X}", display_id);
+ const Result rc = nv_flinger.CloseDisplay(display_id) ? ResultSuccess : ResultUnknown;
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(rc);
}
// This literally does nothing internally in the actual service itself,
diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp
index 7d5d37bbc..1e1c42cea 100644
--- a/src/core/internal_network/socket_proxy.cpp
+++ b/src/core/internal_network/socket_proxy.cpp
@@ -11,6 +11,10 @@
#include "core/internal_network/network_interface.h"
#include "core/internal_network/socket_proxy.h"
+#if YUZU_UNIX
+#include <sys/socket.h>
+#endif
+
namespace Network {
ProxySocket::ProxySocket(RoomNetwork& room_network_) noexcept : room_network{room_network_} {}
diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp
index f4dd24e7d..826fa2109 100644
--- a/src/input_common/drivers/gc_adapter.cpp
+++ b/src/input_common/drivers/gc_adapter.cpp
@@ -324,7 +324,7 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) {
return true;
}
-Common::Input::VibrationError GCAdapter::SetRumble(
+Common::Input::VibrationError GCAdapter::SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f;
const auto processed_amplitude =
@@ -338,6 +338,10 @@ Common::Input::VibrationError GCAdapter::SetRumble(
return Common::Input::VibrationError::None;
}
+bool GCAdapter::IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {
+ return rumble_enabled;
+}
+
void GCAdapter::UpdateVibrations() {
// Use 8 states to keep the switching between on/off fast enough for
// a human to feel different vibration strenght
diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h
index 8682da847..7f81767f7 100644
--- a/src/input_common/drivers/gc_adapter.h
+++ b/src/input_common/drivers/gc_adapter.h
@@ -25,9 +25,11 @@ public:
explicit GCAdapter(std::string input_engine_);
~GCAdapter() override;
- Common::Input::VibrationError SetRumble(
+ Common::Input::VibrationError SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
+ bool IsVibrationEnabled(const PadIdentifier& identifier) override;
+
/// Used for automapping features
std::vector<Common::ParamPackage> GetInputDevices() const override;
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index c175a8853..45ce588f0 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -114,6 +114,20 @@ public:
}
return false;
}
+
+ void EnableVibration(bool is_enabled) {
+ has_vibration = is_enabled;
+ is_vibration_tested = true;
+ }
+
+ bool HasVibration() const {
+ return has_vibration;
+ }
+
+ bool IsVibrationTested() const {
+ return is_vibration_tested;
+ }
+
/**
* The Pad identifier of the joystick
*/
@@ -236,6 +250,8 @@ private:
u64 last_motion_update{};
bool has_gyro{false};
bool has_accel{false};
+ bool has_vibration{false};
+ bool is_vibration_tested{false};
BasicMotion motion;
};
@@ -517,7 +533,7 @@ std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const {
return devices;
}
-Common::Input::VibrationError SDLDriver::SetRumble(
+Common::Input::VibrationError SDLDriver::SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
const auto joystick =
GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port));
@@ -546,13 +562,6 @@ Common::Input::VibrationError SDLDriver::SetRumble(
.type = Common::Input::VibrationAmplificationType::Exponential,
};
- if (vibration.type == Common::Input::VibrationAmplificationType::Test) {
- if (!joystick->RumblePlay(new_vibration)) {
- return Common::Input::VibrationError::Unknown;
- }
- return Common::Input::VibrationError::None;
- }
-
vibration_queue.Push(VibrationRequest{
.identifier = identifier,
.vibration = new_vibration,
@@ -561,6 +570,45 @@ Common::Input::VibrationError SDLDriver::SetRumble(
return Common::Input::VibrationError::None;
}
+bool SDLDriver::IsVibrationEnabled(const PadIdentifier& identifier) {
+ const auto joystick =
+ GetSDLJoystickByGUID(identifier.guid.RawString(), static_cast<int>(identifier.port));
+
+ constexpr Common::Input::VibrationStatus test_vibration{
+ .low_amplitude = 1,
+ .low_frequency = 160.0f,
+ .high_amplitude = 1,
+ .high_frequency = 320.0f,
+ .type = Common::Input::VibrationAmplificationType::Exponential,
+ };
+
+ constexpr Common::Input::VibrationStatus zero_vibration{
+ .low_amplitude = 0,
+ .low_frequency = 160.0f,
+ .high_amplitude = 0,
+ .high_frequency = 320.0f,
+ .type = Common::Input::VibrationAmplificationType::Exponential,
+ };
+
+ if (joystick->IsVibrationTested()) {
+ return joystick->HasVibration();
+ }
+
+ // First vibration might fail
+ joystick->RumblePlay(test_vibration);
+
+ // Wait for about 15ms to ensure the controller is ready for the stop command
+ std::this_thread::sleep_for(std::chrono::milliseconds(15));
+
+ if (!joystick->RumblePlay(zero_vibration)) {
+ joystick->EnableVibration(false);
+ return false;
+ }
+
+ joystick->EnableVibration(true);
+ return true;
+}
+
void SDLDriver::SendVibrations() {
while (!vibration_queue.Empty()) {
VibrationRequest request;
diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h
index fc3a44572..d1b4471cf 100644
--- a/src/input_common/drivers/sdl_driver.h
+++ b/src/input_common/drivers/sdl_driver.h
@@ -61,9 +61,11 @@ public:
bool IsStickInverted(const Common::ParamPackage& params) override;
- Common::Input::VibrationError SetRumble(
+ Common::Input::VibrationError SetVibration(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
+ bool IsVibrationEnabled(const PadIdentifier& identifier) override;
+
private:
struct VibrationRequest {
PadIdentifier identifier;
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index cfbdb26bd..d4c264a8e 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -108,12 +108,17 @@ public:
[[maybe_unused]] const Common::Input::LedStatus& led_status) {}
// Sets rumble to a controller
- virtual Common::Input::VibrationError SetRumble(
+ virtual Common::Input::VibrationError SetVibration(
[[maybe_unused]] const PadIdentifier& identifier,
[[maybe_unused]] const Common::Input::VibrationStatus& vibration) {
return Common::Input::VibrationError::NotSupported;
}
+ // Returns true if device supports vibrations
+ virtual bool IsVibrationEnabled([[maybe_unused]] const PadIdentifier& identifier) {
+ return false;
+ }
+
// Sets polling mode to a controller
virtual Common::Input::PollingError SetPollingMode(
[[maybe_unused]] const PadIdentifier& identifier,
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index ccc3076ca..4ac182147 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -763,7 +763,11 @@ public:
Common::Input::VibrationError SetVibration(
const Common::Input::VibrationStatus& vibration_status) override {
- return input_engine->SetRumble(identifier, vibration_status);
+ return input_engine->SetVibration(identifier, vibration_status);
+ }
+
+ bool IsVibrationEnabled() override {
+ return input_engine->IsVibrationEnabled(identifier);
}
Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override {
diff --git a/src/shader_recompiler/CMakeLists.txt b/src/shader_recompiler/CMakeLists.txt
index bcdd60db9..545d69c7e 100644
--- a/src/shader_recompiler/CMakeLists.txt
+++ b/src/shader_recompiler/CMakeLists.txt
@@ -224,6 +224,7 @@ add_library(shader_recompiler STATIC
ir_opt/lower_fp16_to_fp32.cpp
ir_opt/lower_int64_to_int32.cpp
ir_opt/passes.h
+ ir_opt/position_pass.cpp
ir_opt/rescaling_pass.cpp
ir_opt/ssa_rewrite_pass.cpp
ir_opt/texture_pass.cpp
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm.cpp b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
index 01f9abc71..3b0176bf6 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm.cpp
@@ -450,6 +450,9 @@ std::string EmitGLASM(const Profile& profile, const RuntimeInfo& runtime_info, I
if (program.info.uses_rescaling_uniform) {
header += "PARAM scaling[1]={program.local[0..0]};";
}
+ if (program.info.uses_render_area) {
+ header += "PARAM render_area[1]={program.local[1..1]};";
+ }
header += "TEMP ";
for (size_t index = 0; index < ctx.reg_alloc.NumUsedRegisters(); ++index) {
header += fmt::format("R{},", index);
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
index 2fc2a0ac6..5bfdecc09 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_bitwise_conversion.cpp
@@ -43,6 +43,10 @@ void EmitBitCastU64F64(EmitContext&, IR::Inst& inst, const IR::Value& value) {
Alias(inst, value);
}
+void EmitBitCastS32F32(EmitContext&, IR::Inst& inst, const IR::Value& value) {
+ Alias(inst, value);
+}
+
void EmitBitCastF16U16(EmitContext&, IR::Inst& inst, const IR::Value& value) {
Alias(inst, value);
}
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
index 7e8f37563..d6562c842 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
@@ -379,6 +379,18 @@ void EmitInvocationId(EmitContext& ctx, IR::Inst& inst) {
ctx.Add("MOV.S {}.x,primitive_invocation.x;", inst);
}
+void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) {
+ switch (ctx.stage) {
+ case Stage::TessellationControl:
+ case Stage::TessellationEval:
+ ctx.Add("SHL.U {}.x,primitive.vertexcount,16;", inst);
+ break;
+ default:
+ LOG_WARNING(Shader, "(STUBBED) called");
+ ctx.Add("MOV.S {}.x,0x00ff0000;", inst);
+ }
+}
+
void EmitSampleId(EmitContext& ctx, IR::Inst& inst) {
ctx.Add("MOV.S {}.x,fragment.sampleid.x;", inst);
}
@@ -396,6 +408,10 @@ void EmitResolutionDownFactor(EmitContext& ctx, IR::Inst& inst) {
ctx.Add("MOV.F {}.x,scaling[0].z;", inst);
}
+void EmitRenderArea(EmitContext& ctx, IR::Inst& inst) {
+ ctx.Add("MOV.F {},render_area[0];", inst);
+}
+
void EmitLoadLocal(EmitContext& ctx, IR::Inst& inst, ScalarU32 word_offset) {
ctx.Add("MOV.U {},lmem[{}].x;", inst, word_offset);
}
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
index 8b0ac3031..eaaf9ba39 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_instructions.h
@@ -69,10 +69,12 @@ void EmitSetOFlag(EmitContext& ctx);
void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst);
void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst);
void EmitInvocationId(EmitContext& ctx, IR::Inst& inst);
+void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst);
void EmitSampleId(EmitContext& ctx, IR::Inst& inst);
void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst);
void EmitYDirection(EmitContext& ctx, IR::Inst& inst);
void EmitResolutionDownFactor(EmitContext& ctx, IR::Inst& inst);
+void EmitRenderArea(EmitContext& ctx, IR::Inst& inst);
void EmitLoadLocal(EmitContext& ctx, IR::Inst& inst, ScalarU32 word_offset);
void EmitWriteLocal(EmitContext& ctx, ScalarU32 word_offset, ScalarU32 value);
void EmitUndefU1(EmitContext& ctx, IR::Inst& inst);
@@ -195,6 +197,7 @@ void EmitSelectF64(EmitContext& ctx, ScalarS32 cond, Register true_value, Regist
void EmitBitCastU16F16(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
void EmitBitCastU32F32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
+void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
void EmitBitCastF16U16(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
void EmitBitCastF32U32(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
void EmitBitCastF64U64(EmitContext& ctx, IR::Inst& inst, const IR::Value& value);
diff --git a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp
index 89603c1c4..333a91cc5 100644
--- a/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp
+++ b/src/shader_recompiler/backend/glasm/glasm_emit_context.cpp
@@ -95,6 +95,10 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
if (info.uses_invocation_id) {
Add("ATTRIB primitive_invocation=primitive.invocation;");
}
+ if (info.uses_invocation_info &&
+ (stage == Stage::TessellationControl || stage == Stage::TessellationEval)) {
+ Add("ATTRIB primitive_vertexcount = primitive.vertexcount;");
+ }
if (info.stores_tess_level_outer) {
Add("OUTPUT result_patch_tessouter[]={{result.patch.tessouter[0..3]}};");
}
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
index 1be4a0f59..8e5e6cf1f 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_bitwise_conversion.cpp
@@ -48,6 +48,10 @@ void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, std::string_view value)
ctx.AddU64("{}=doubleBitsToUint64({});", inst, value);
}
+void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value) {
+ ctx.AddF32("{}=ftoi({});", inst, value);
+}
+
void EmitBitCastF16U16([[maybe_unused]] EmitContext& ctx, [[maybe_unused]] IR::Inst& inst) {
NotImplemented();
}
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
index fad8d1e30..c1671c37b 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_context_get_set.cpp
@@ -399,6 +399,18 @@ void EmitInvocationId(EmitContext& ctx, IR::Inst& inst) {
ctx.AddU32("{}=uint(gl_InvocationID);", inst);
}
+void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst) {
+ switch (ctx.stage) {
+ case Stage::TessellationControl:
+ case Stage::TessellationEval:
+ ctx.AddU32("{}=uint(gl_PatchVerticesIn)<<16;", inst);
+ break;
+ default:
+ LOG_WARNING(Shader, "(STUBBED) called");
+ ctx.AddU32("{}=uint(0x00ff0000);", inst);
+ }
+}
+
void EmitSampleId(EmitContext& ctx, IR::Inst& inst) {
ctx.AddU32("{}=uint(gl_SampleID);", inst);
}
@@ -416,6 +428,10 @@ void EmitResolutionDownFactor(EmitContext& ctx, IR::Inst& inst) {
ctx.AddF32("{}=scaling.z;", inst);
}
+void EmitRenderArea(EmitContext& ctx, IR::Inst& inst) {
+ ctx.AddF32x4("{}=render_area;", inst);
+}
+
void EmitLoadLocal(EmitContext& ctx, IR::Inst& inst, std::string_view word_offset) {
ctx.AddU32("{}=lmem[{}];", inst, word_offset);
}
diff --git a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
index 639691ba6..4151c89de 100644
--- a/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
+++ b/src/shader_recompiler/backend/glsl/emit_glsl_instructions.h
@@ -83,10 +83,12 @@ void EmitSetOFlag(EmitContext& ctx);
void EmitWorkgroupId(EmitContext& ctx, IR::Inst& inst);
void EmitLocalInvocationId(EmitContext& ctx, IR::Inst& inst);
void EmitInvocationId(EmitContext& ctx, IR::Inst& inst);
+void EmitInvocationInfo(EmitContext& ctx, IR::Inst& inst);
void EmitSampleId(EmitContext& ctx, IR::Inst& inst);
void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst);
void EmitYDirection(EmitContext& ctx, IR::Inst& inst);
void EmitResolutionDownFactor(EmitContext& ctx, IR::Inst& inst);
+void EmitRenderArea(EmitContext& ctx, IR::Inst& inst);
void EmitLoadLocal(EmitContext& ctx, IR::Inst& inst, std::string_view word_offset);
void EmitWriteLocal(EmitContext& ctx, std::string_view word_offset, std::string_view value);
void EmitUndefU1(EmitContext& ctx, IR::Inst& inst);
@@ -229,6 +231,7 @@ void EmitSelectF64(EmitContext& ctx, IR::Inst& inst, std::string_view cond,
void EmitBitCastU16F16(EmitContext& ctx, IR::Inst& inst);
void EmitBitCastU32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value);
void EmitBitCastU64F64(EmitContext& ctx, IR::Inst& inst, std::string_view value);
+void EmitBitCastS32F32(EmitContext& ctx, IR::Inst& inst, std::string_view value);
void EmitBitCastF16U16(EmitContext& ctx, IR::Inst& inst);
void EmitBitCastF32U32(EmitContext& ctx, IR::Inst& inst, std::string_view value);
void EmitBitCastF64U64(EmitContext& ctx, IR::Inst& inst, std::string_view value);
diff --git a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
index c767a9dc3..5d01ec0cd 100644
--- a/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
+++ b/src/shader_recompiler/backend/glsl/glsl_emit_context.cpp
@@ -358,6 +358,9 @@ EmitContext::EmitContext(IR::Program& program, Bindings& bindings, const Profile
if (info.uses_rescaling_uniform) {
header += "layout(location=0) uniform vec4 scaling;";
}
+ if (info.uses_render_area) {
+ header += "layout(location=1) uniform vec4 render_area;";
+ }
DefineConstantBuffers(bindings);
DefineConstantBufferIndirect();
DefineStorageBuffers(bindings);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.h b/src/shader_recompiler/backend/spirv/emit_spirv.h
index 7567b6fc9..937881484 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv.h
@@ -23,8 +23,12 @@ struct RescalingLayout {
alignas(16) std::array<u32, NUM_IMAGE_SCALING_WORDS> rescaling_images;
u32 down_factor;
};
+struct RenderAreaLayout {
+ std::array<f32, 4> render_area;
+};
constexpr u32 RESCALING_LAYOUT_WORDS_OFFSET = offsetof(RescalingLayout, rescaling_textures);
constexpr u32 RESCALING_LAYOUT_DOWN_FACTOR_OFFSET = offsetof(RescalingLayout, down_factor);
+constexpr u32 RENDERAREA_LAYOUT_OFFSET = offsetof(RenderAreaLayout, render_area);
[[nodiscard]] std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info,
IR::Program& program, Bindings& bindings);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
index c4ca28d11..50daacd95 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_bitwise_conversion.cpp
@@ -18,6 +18,10 @@ void EmitBitCastU64F64(EmitContext&) {
throw NotImplementedException("SPIR-V Instruction");
}
+void EmitBitCastS32F32(EmitContext&) {
+ throw NotImplementedException("SPIR-V Instruction");
+}
+
void EmitBitCastF16U16(EmitContext&) {
throw NotImplementedException("SPIR-V Instruction");
}
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
index 2c68aba39..5b3b5d1f3 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
@@ -353,7 +353,6 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {
case IR::Attribute::TessellationEvaluationPointV:
return ctx.OpLoad(ctx.F32[1],
ctx.OpAccessChain(ctx.input_f32, ctx.tess_coord, ctx.Const(1U)));
-
default:
throw NotImplementedException("Read attribute {}", attr);
}
@@ -513,6 +512,18 @@ Id EmitInvocationId(EmitContext& ctx) {
return ctx.OpLoad(ctx.U32[1], ctx.invocation_id);
}
+Id EmitInvocationInfo(EmitContext& ctx) {
+ switch (ctx.stage) {
+ case Stage::TessellationControl:
+ case Stage::TessellationEval:
+ return ctx.OpShiftLeftLogical(ctx.U32[1], ctx.OpLoad(ctx.U32[1], ctx.patch_vertices_in),
+ ctx.Const(16u));
+ default:
+ LOG_WARNING(Shader, "(STUBBED) called");
+ return ctx.Const(0x00ff0000u);
+ }
+}
+
Id EmitSampleId(EmitContext& ctx) {
return ctx.OpLoad(ctx.U32[1], ctx.sample_id);
}
@@ -537,6 +548,17 @@ Id EmitResolutionDownFactor(EmitContext& ctx) {
}
}
+Id EmitRenderArea(EmitContext& ctx) {
+ if (ctx.profile.unified_descriptor_binding) {
+ const Id pointer_type{ctx.TypePointer(spv::StorageClass::PushConstant, ctx.F32[4])};
+ const Id index{ctx.Const(ctx.render_are_member_index)};
+ const Id pointer{ctx.OpAccessChain(pointer_type, ctx.render_area_push_constant, index)};
+ return ctx.OpLoad(ctx.F32[4], pointer);
+ } else {
+ throw NotImplementedException("SPIR-V Instruction");
+ }
+}
+
Id EmitLoadLocal(EmitContext& ctx, Id word_offset) {
const Id pointer{ctx.OpAccessChain(ctx.private_u32, ctx.local_memory, word_offset)};
return ctx.OpLoad(ctx.U32[1], pointer);
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
index 984d072b4..e31cdc5e8 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h
@@ -72,10 +72,12 @@ void EmitSetOFlag(EmitContext& ctx);
Id EmitWorkgroupId(EmitContext& ctx);
Id EmitLocalInvocationId(EmitContext& ctx);
Id EmitInvocationId(EmitContext& ctx);
+Id EmitInvocationInfo(EmitContext& ctx);
Id EmitSampleId(EmitContext& ctx);
Id EmitIsHelperInvocation(EmitContext& ctx);
Id EmitYDirection(EmitContext& ctx);
Id EmitResolutionDownFactor(EmitContext& ctx);
+Id EmitRenderArea(EmitContext& ctx);
Id EmitLoadLocal(EmitContext& ctx, Id word_offset);
void EmitWriteLocal(EmitContext& ctx, Id word_offset, Id value);
Id EmitUndefU1(EmitContext& ctx);
@@ -177,7 +179,8 @@ Id EmitSelectF64(EmitContext& ctx, Id cond, Id true_value, Id false_value);
void EmitBitCastU16F16(EmitContext& ctx);
Id EmitBitCastU32F32(EmitContext& ctx, Id value);
void EmitBitCastU64F64(EmitContext& ctx);
-void EmitBitCastF16U16(EmitContext& ctx);
+void EmitBitCastS32F32(EmitContext& ctx);
+void EmitBitCastF16U16(EmitContext&);
Id EmitBitCastF32U32(EmitContext& ctx, Id value);
void EmitBitCastF64U64(EmitContext& ctx);
Id EmitPackUint2x32(EmitContext& ctx, Id value);
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index aecc4c612..0bfc2dd89 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -473,6 +473,7 @@ EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_inf
DefineAttributeMemAccess(program.info);
DefineGlobalMemoryFunctions(program.info);
DefineRescalingInput(program.info);
+ DefineRenderArea(program.info);
}
EmitContext::~EmitContext() = default;
@@ -982,6 +983,36 @@ void EmitContext::DefineRescalingInputUniformConstant() {
}
}
+void EmitContext::DefineRenderArea(const Info& info) {
+ if (!info.uses_render_area) {
+ return;
+ }
+
+ if (profile.unified_descriptor_binding) {
+ boost::container::static_vector<Id, 1> members{};
+ u32 member_index{0};
+
+ members.push_back(F32[4]);
+ render_are_member_index = member_index++;
+
+ const Id push_constant_struct{TypeStruct(std::span(members.data(), members.size()))};
+ Decorate(push_constant_struct, spv::Decoration::Block);
+ Name(push_constant_struct, "RenderAreaInfo");
+
+ MemberDecorate(push_constant_struct, render_are_member_index, spv::Decoration::Offset, 0);
+ MemberName(push_constant_struct, render_are_member_index, "render_area");
+
+ const Id pointer_type{TypePointer(spv::StorageClass::PushConstant, push_constant_struct)};
+ render_area_push_constant =
+ AddGlobalVariable(pointer_type, spv::StorageClass::PushConstant);
+ Name(render_area_push_constant, "render_area_push_constants");
+
+ if (profile.supported_spirv >= 0x00010400) {
+ interfaces.push_back(render_area_push_constant);
+ }
+ }
+}
+
void EmitContext::DefineConstantBuffers(const Info& info, u32& binding) {
if (info.constant_buffer_descriptors.empty()) {
return;
@@ -1294,6 +1325,10 @@ void EmitContext::DefineInputs(const IR::Program& program) {
if (info.uses_invocation_id) {
invocation_id = DefineInput(*this, U32[1], false, spv::BuiltIn::InvocationId);
}
+ if (info.uses_invocation_info &&
+ (stage == Shader::Stage::TessellationControl || stage == Shader::Stage::TessellationEval)) {
+ patch_vertices_in = DefineInput(*this, U32[1], false, spv::BuiltIn::PatchVertices);
+ }
if (info.uses_sample_id) {
sample_id = DefineInput(*this, U32[1], false, spv::BuiltIn::SampleId);
}
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
index bc25b8b84..dde45b4bc 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h
@@ -204,6 +204,7 @@ public:
Id workgroup_id{};
Id local_invocation_id{};
Id invocation_id{};
+ Id patch_vertices_in{};
Id sample_id{};
Id is_helper_invocation{};
Id subgroup_local_invocation_id{};
@@ -243,6 +244,9 @@ public:
u32 texture_rescaling_index{};
u32 image_rescaling_index{};
+ Id render_area_push_constant{};
+ u32 render_are_member_index{};
+
Id local_memory{};
Id shared_memory_u8{};
@@ -318,6 +322,7 @@ private:
void DefineRescalingInput(const Info& info);
void DefineRescalingInputPushConstant();
void DefineRescalingInputUniformConstant();
+ void DefineRenderArea(const Info& info);
void DefineInputs(const IR::Program& program);
void DefineOutputs(const IR::Program& program);
diff --git a/src/shader_recompiler/environment.h b/src/shader_recompiler/environment.h
index 9729d48c6..402f2664f 100644
--- a/src/shader_recompiler/environment.h
+++ b/src/shader_recompiler/environment.h
@@ -22,6 +22,10 @@ public:
[[nodiscard]] virtual TextureType ReadTextureType(u32 raw_handle) = 0;
+ [[nodiscard]] virtual TexturePixelFormat ReadTexturePixelFormat(u32 raw_handle) = 0;
+
+ [[nodiscard]] virtual u32 ReadViewportTransformState() = 0;
+
[[nodiscard]] virtual u32 TextureBoundBuffer() const = 0;
[[nodiscard]] virtual u32 LocalMemorySize() const = 0;
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
index 11086ed8c..0cdac0eff 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.cpp
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
@@ -362,6 +362,10 @@ U32 IREmitter::InvocationId() {
return Inst<U32>(Opcode::InvocationId);
}
+U32 IREmitter::InvocationInfo() {
+ return Inst<U32>(Opcode::InvocationInfo);
+}
+
U32 IREmitter::SampleId() {
return Inst<U32>(Opcode::SampleId);
}
@@ -378,6 +382,14 @@ F32 IREmitter::ResolutionDownFactor() {
return Inst<F32>(Opcode::ResolutionDownFactor);
}
+F32 IREmitter::RenderAreaWidth() {
+ return F32(CompositeExtract(Inst<Value>(Opcode::RenderArea), 0));
+}
+
+F32 IREmitter::RenderAreaHeight() {
+ return F32(CompositeExtract(Inst<Value>(Opcode::RenderArea), 1));
+}
+
U32 IREmitter::LaneId() {
return Inst<U32>(Opcode::LaneId);
}
@@ -684,6 +696,11 @@ IR::U32 IREmitter::BitCast<IR::U32, IR::F32>(const IR::F32& value) {
}
template <>
+IR::S32 IREmitter::BitCast<IR::S32, IR::F32>(const IR::F32& value) {
+ return Inst<IR::S32>(Opcode::BitCastS32F32, value);
+}
+
+template <>
IR::F32 IREmitter::BitCast<IR::F32, IR::U32>(const IR::U32& value) {
return Inst<IR::F32>(Opcode::BitCastF32U32, value);
}
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h
index 25839a371..2df992feb 100644
--- a/src/shader_recompiler/frontend/ir/ir_emitter.h
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.h
@@ -97,12 +97,16 @@ public:
[[nodiscard]] U32 LocalInvocationIdZ();
[[nodiscard]] U32 InvocationId();
+ [[nodiscard]] U32 InvocationInfo();
[[nodiscard]] U32 SampleId();
[[nodiscard]] U1 IsHelperInvocation();
[[nodiscard]] F32 YDirection();
[[nodiscard]] F32 ResolutionDownFactor();
+ [[nodiscard]] F32 RenderAreaWidth();
+ [[nodiscard]] F32 RenderAreaHeight();
+
[[nodiscard]] U32 LaneId();
[[nodiscard]] U32 LoadGlobalU8(const U64& address);
diff --git a/src/shader_recompiler/frontend/ir/microinstruction.cpp b/src/shader_recompiler/frontend/ir/microinstruction.cpp
index 468782eb1..84417980b 100644
--- a/src/shader_recompiler/frontend/ir/microinstruction.cpp
+++ b/src/shader_recompiler/frontend/ir/microinstruction.cpp
@@ -325,11 +325,6 @@ void Inst::AddPhiOperand(Block* predecessor, const Value& value) {
phi_args.emplace_back(predecessor, value);
}
-void Inst::ErasePhiOperand(size_t index) {
- const auto operand_it{phi_args.begin() + static_cast<ptrdiff_t>(index)};
- phi_args.erase(operand_it);
-}
-
void Inst::OrderPhiArgs() {
if (op != Opcode::Phi) {
throw LogicError("{} is not a Phi instruction", op);
diff --git a/src/shader_recompiler/frontend/ir/opcodes.h b/src/shader_recompiler/frontend/ir/opcodes.h
index 752879a18..e70d7745c 100644
--- a/src/shader_recompiler/frontend/ir/opcodes.h
+++ b/src/shader_recompiler/frontend/ir/opcodes.h
@@ -37,6 +37,7 @@ constexpr Type U8{Type::U8};
constexpr Type U16{Type::U16};
constexpr Type U32{Type::U32};
constexpr Type U64{Type::U64};
+constexpr Type S32{Type::S32};
constexpr Type F16{Type::F16};
constexpr Type F32{Type::F32};
constexpr Type F64{Type::F64};
diff --git a/src/shader_recompiler/frontend/ir/opcodes.inc b/src/shader_recompiler/frontend/ir/opcodes.inc
index 86410ddfc..1fe3749cc 100644
--- a/src/shader_recompiler/frontend/ir/opcodes.inc
+++ b/src/shader_recompiler/frontend/ir/opcodes.inc
@@ -59,10 +59,12 @@ OPCODE(SetOFlag, Void, U1,
OPCODE(WorkgroupId, U32x3, )
OPCODE(LocalInvocationId, U32x3, )
OPCODE(InvocationId, U32, )
+OPCODE(InvocationInfo, U32, )
OPCODE(SampleId, U32, )
OPCODE(IsHelperInvocation, U1, )
OPCODE(YDirection, F32, )
OPCODE(ResolutionDownFactor, F32, )
+OPCODE(RenderArea, F32x4, )
// Undefined
OPCODE(UndefU1, U1, )
@@ -173,6 +175,7 @@ OPCODE(SelectF64, F64, U1,
OPCODE(BitCastU16F16, U16, F16, )
OPCODE(BitCastU32F32, U32, F32, )
OPCODE(BitCastU64F64, U64, F64, )
+OPCODE(BitCastS32F32, S32, F32, )
OPCODE(BitCastF16U16, F16, U16, )
OPCODE(BitCastF32U32, F32, U32, )
OPCODE(BitCastF64U64, F64, U64, )
diff --git a/src/shader_recompiler/frontend/ir/patch.h b/src/shader_recompiler/frontend/ir/patch.h
index 1e37c8eb6..5077e56c2 100644
--- a/src/shader_recompiler/frontend/ir/patch.h
+++ b/src/shader_recompiler/frontend/ir/patch.h
@@ -14,8 +14,6 @@ enum class Patch : u64 {
TessellationLodBottom,
TessellationLodInteriorU,
TessellationLodInteriorV,
- ComponentPadding0,
- ComponentPadding1,
Component0,
Component1,
Component2,
@@ -137,7 +135,7 @@ enum class Patch : u64 {
Component118,
Component119,
};
-static_assert(static_cast<u64>(Patch::Component119) == 127);
+static_assert(static_cast<u64>(Patch::Component119) == 125);
[[nodiscard]] bool IsGeneric(Patch patch) noexcept;
diff --git a/src/shader_recompiler/frontend/ir/type.h b/src/shader_recompiler/frontend/ir/type.h
index 04c8c4ddb..5a7c706ad 100644
--- a/src/shader_recompiler/frontend/ir/type.h
+++ b/src/shader_recompiler/frontend/ir/type.h
@@ -24,21 +24,22 @@ enum class Type {
U16 = 1 << 7,
U32 = 1 << 8,
U64 = 1 << 9,
- F16 = 1 << 10,
- F32 = 1 << 11,
- F64 = 1 << 12,
- U32x2 = 1 << 13,
- U32x3 = 1 << 14,
- U32x4 = 1 << 15,
- F16x2 = 1 << 16,
- F16x3 = 1 << 17,
- F16x4 = 1 << 18,
- F32x2 = 1 << 19,
- F32x3 = 1 << 20,
- F32x4 = 1 << 21,
- F64x2 = 1 << 22,
- F64x3 = 1 << 23,
- F64x4 = 1 << 24,
+ S32 = 1 << 10,
+ F16 = 1 << 11,
+ F32 = 1 << 12,
+ F64 = 1 << 13,
+ U32x2 = 1 << 14,
+ U32x3 = 1 << 15,
+ U32x4 = 1 << 16,
+ F16x2 = 1 << 17,
+ F16x3 = 1 << 18,
+ F16x4 = 1 << 19,
+ F32x2 = 1 << 20,
+ F32x3 = 1 << 21,
+ F32x4 = 1 << 22,
+ F64x2 = 1 << 23,
+ F64x3 = 1 << 24,
+ F64x4 = 1 << 25,
};
DECLARE_ENUM_FLAG_OPERATORS(Type)
diff --git a/src/shader_recompiler/frontend/ir/value.cpp b/src/shader_recompiler/frontend/ir/value.cpp
index 346169328..30ba12316 100644
--- a/src/shader_recompiler/frontend/ir/value.cpp
+++ b/src/shader_recompiler/frontend/ir/value.cpp
@@ -23,6 +23,8 @@ Value::Value(u16 value) noexcept : type{Type::U16}, imm_u16{value} {}
Value::Value(u32 value) noexcept : type{Type::U32}, imm_u32{value} {}
+Value::Value(s32 value) noexcept : type{Type::S32}, imm_s32{value} {}
+
Value::Value(f32 value) noexcept : type{Type::F32}, imm_f32{value} {}
Value::Value(u64 value) noexcept : type{Type::U64}, imm_u64{value} {}
@@ -69,6 +71,7 @@ bool Value::operator==(const Value& other) const {
return imm_u16 == other.imm_u16;
case Type::U32:
case Type::F32:
+ case Type::S32:
return imm_u32 == other.imm_u32;
case Type::U64:
case Type::F64:
diff --git a/src/shader_recompiler/frontend/ir/value.h b/src/shader_recompiler/frontend/ir/value.h
index 1a2e4ccb6..e8bbb93a5 100644
--- a/src/shader_recompiler/frontend/ir/value.h
+++ b/src/shader_recompiler/frontend/ir/value.h
@@ -44,6 +44,7 @@ public:
explicit Value(u8 value) noexcept;
explicit Value(u16 value) noexcept;
explicit Value(u32 value) noexcept;
+ explicit Value(s32 value) noexcept;
explicit Value(f32 value) noexcept;
explicit Value(u64 value) noexcept;
explicit Value(f64 value) noexcept;
@@ -66,6 +67,7 @@ public:
[[nodiscard]] u8 U8() const;
[[nodiscard]] u16 U16() const;
[[nodiscard]] u32 U32() const;
+ [[nodiscard]] s32 S32() const;
[[nodiscard]] f32 F32() const;
[[nodiscard]] u64 U64() const;
[[nodiscard]] f64 F64() const;
@@ -85,6 +87,7 @@ private:
u8 imm_u8;
u16 imm_u16;
u32 imm_u32;
+ s32 imm_s32;
f32 imm_f32;
u64 imm_u64;
f64 imm_f64;
@@ -178,13 +181,9 @@ public:
/// Get a pointer to the block of a phi argument.
[[nodiscard]] Block* PhiBlock(size_t index) const;
-
/// Add phi operand to a phi instruction.
void AddPhiOperand(Block* predecessor, const Value& value);
- // Erase the phi operand at the given index.
- void ErasePhiOperand(size_t index);
-
/// Orders the Phi arguments from farthest away to nearest.
void OrderPhiArgs();
@@ -270,6 +269,7 @@ using U8 = TypedValue<Type::U8>;
using U16 = TypedValue<Type::U16>;
using U32 = TypedValue<Type::U32>;
using U64 = TypedValue<Type::U64>;
+using S32 = TypedValue<Type::S32>;
using F16 = TypedValue<Type::F16>;
using F32 = TypedValue<Type::F32>;
using F64 = TypedValue<Type::F64>;
@@ -381,6 +381,14 @@ inline u32 Value::U32() const {
return imm_u32;
}
+inline s32 Value::S32() const {
+ if (IsIdentity()) {
+ return inst->Arg(0).S32();
+ }
+ DEBUG_ASSERT(type == Type::S32);
+ return imm_s32;
+}
+
inline f32 Value::F32() const {
if (IsIdentity()) {
return inst->Arg(0).F32();
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
index 52be12f9c..753c62098 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/move_special_register.cpp
@@ -117,8 +117,7 @@ enum class SpecialRegister : u64 {
case SpecialRegister::SR_THREAD_KILL:
return IR::U32{ir.Select(ir.IsHelperInvocation(), ir.Imm32(-1), ir.Imm32(0))};
case SpecialRegister::SR_INVOCATION_INFO:
- LOG_WARNING(Shader, "(STUBBED) SR_INVOCATION_INFO");
- return ir.Imm32(0x00ff'0000);
+ return ir.InvocationInfo();
case SpecialRegister::SR_TID: {
const IR::Value tid{ir.LocalInvocationId()};
return ir.BitFieldInsert(ir.BitFieldInsert(IR::U32{ir.CompositeExtract(tid, 0)},
diff --git a/src/shader_recompiler/frontend/maxwell/translate_program.cpp b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
index b58741d4d..376aae0ea 100644
--- a/src/shader_recompiler/frontend/maxwell/translate_program.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate_program.cpp
@@ -220,8 +220,10 @@ IR::Program TranslateProgram(ObjectPool<IR::Inst>& inst_pool, ObjectPool<IR::Blo
Optimization::ConstantPropagationPass(program);
+ Optimization::PositionPass(env, program);
+
Optimization::GlobalMemoryToStorageBufferPass(program);
- Optimization::TexturePass(env, program);
+ Optimization::TexturePass(env, program, host_info);
if (Settings::values.resolution_info.active) {
Optimization::RescalingPass(program);
diff --git a/src/shader_recompiler/host_translate_info.h b/src/shader_recompiler/host_translate_info.h
index 881874310..cc1500690 100644
--- a/src/shader_recompiler/host_translate_info.h
+++ b/src/shader_recompiler/host_translate_info.h
@@ -13,6 +13,7 @@ struct HostTranslateInfo {
bool support_float16{}; ///< True when the device supports 16-bit floats
bool support_int64{}; ///< True when the device supports 64-bit integers
bool needs_demote_reorder{}; ///< True when the device needs DemoteToHelperInvocation reordered
+ bool support_snorm_render_buffer{}; ///< True when the device supports SNORM render buffers
};
} // namespace Shader
diff --git a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
index 7cff8ecdc..5a4195217 100644
--- a/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
+++ b/src/shader_recompiler/ir_opt/collect_shader_info_pass.cpp
@@ -468,6 +468,9 @@ void VisitUsages(Info& info, IR::Inst& inst) {
case IR::Opcode::InvocationId:
info.uses_invocation_id = true;
break;
+ case IR::Opcode::InvocationInfo:
+ info.uses_invocation_info = true;
+ break;
case IR::Opcode::SampleId:
info.uses_sample_id = true;
break;
diff --git a/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp b/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp
index 9a7d47344..1bd8afd6f 100644
--- a/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp
+++ b/src/shader_recompiler/ir_opt/dead_code_elimination_pass.cpp
@@ -1,104 +1,24 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <algorithm>
-
-#include <boost/container/small_vector.hpp>
-
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/value.h"
#include "shader_recompiler/ir_opt/passes.h"
namespace Shader::Optimization {
-namespace {
-template <bool TEST_USES>
-void DeadInstElimination(IR::Block* const block) {
+
+void DeadCodeEliminationPass(IR::Program& program) {
// We iterate over the instructions in reverse order.
// This is because removing an instruction reduces the number of uses for earlier instructions.
- auto it{block->end()};
- while (it != block->begin()) {
- --it;
- if constexpr (TEST_USES) {
- if (it->HasUses() || it->MayHaveSideEffects()) {
- continue;
- }
- }
- it->Invalidate();
- it = block->Instructions().erase(it);
- }
-}
-
-void DeletedPhiArgElimination(IR::Program& program, std::span<const IR::Block*> dead_blocks) {
- for (IR::Block* const block : program.blocks) {
- for (IR::Inst& phi : *block) {
- if (!IR::IsPhi(phi)) {
- continue;
- }
- for (size_t i = 0; i < phi.NumArgs(); ++i) {
- if (std::ranges::find(dead_blocks, phi.PhiBlock(i)) == dead_blocks.end()) {
- continue;
- }
- // Phi operand at this index is an unreachable block
- phi.ErasePhiOperand(i);
- --i;
- }
- }
- }
-}
-
-void DeadBranchElimination(IR::Program& program) {
- boost::container::small_vector<const IR::Block*, 3> dead_blocks;
- const auto begin_it{program.syntax_list.begin()};
- for (auto node_it = begin_it; node_it != program.syntax_list.end(); ++node_it) {
- if (node_it->type != IR::AbstractSyntaxNode::Type::If) {
- continue;
- }
- IR::Inst* const cond_ref{node_it->data.if_node.cond.Inst()};
- const IR::U1 cond{cond_ref->Arg(0)};
- if (!cond.IsImmediate()) {
- continue;
- }
- if (cond.U1()) {
- continue;
- }
- // False immediate condition. Remove condition ref, erase the entire branch.
- cond_ref->Invalidate();
- // Account for nested if-statements within the if(false) branch
- u32 nested_ifs{1u};
- while (node_it->type != IR::AbstractSyntaxNode::Type::EndIf || nested_ifs > 0) {
- node_it = program.syntax_list.erase(node_it);
- switch (node_it->type) {
- case IR::AbstractSyntaxNode::Type::If:
- ++nested_ifs;
- break;
- case IR::AbstractSyntaxNode::Type::EndIf:
- --nested_ifs;
- break;
- case IR::AbstractSyntaxNode::Type::Block: {
- IR::Block* const block{node_it->data.block};
- DeadInstElimination<false>(block);
- dead_blocks.push_back(block);
- break;
- }
- default:
- break;
+ for (IR::Block* const block : program.post_order_blocks) {
+ auto it{block->end()};
+ while (it != block->begin()) {
+ --it;
+ if (!it->HasUses() && !it->MayHaveSideEffects()) {
+ it->Invalidate();
+ it = block->Instructions().erase(it);
}
}
- // Erase EndIf node of the if(false) branch
- node_it = program.syntax_list.erase(node_it);
- // Account for loop increment
- --node_it;
- }
- if (!dead_blocks.empty()) {
- DeletedPhiArgElimination(program, std::span(dead_blocks.data(), dead_blocks.size()));
- }
-}
-} // namespace
-
-void DeadCodeEliminationPass(IR::Program& program) {
- DeadBranchElimination(program);
- for (IR::Block* const block : program.post_order_blocks) {
- DeadInstElimination<true>(block);
}
}
diff --git a/src/shader_recompiler/ir_opt/passes.h b/src/shader_recompiler/ir_opt/passes.h
index 6ff8e4266..586a0668f 100644
--- a/src/shader_recompiler/ir_opt/passes.h
+++ b/src/shader_recompiler/ir_opt/passes.h
@@ -6,6 +6,10 @@
#include "shader_recompiler/environment.h"
#include "shader_recompiler/frontend/ir/program.h"
+namespace Shader {
+struct HostTranslateInfo;
+}
+
namespace Shader::Optimization {
void CollectShaderInfoPass(Environment& env, IR::Program& program);
@@ -17,7 +21,8 @@ void LowerFp16ToFp32(IR::Program& program);
void LowerInt64ToInt32(IR::Program& program);
void RescalingPass(IR::Program& program);
void SsaRewritePass(IR::Program& program);
-void TexturePass(Environment& env, IR::Program& program);
+void PositionPass(Environment& env, IR::Program& program);
+void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo& host_info);
void VerificationPass(const IR::Program& program);
// Dual Vertex
diff --git a/src/shader_recompiler/ir_opt/position_pass.cpp b/src/shader_recompiler/ir_opt/position_pass.cpp
new file mode 100644
index 000000000..3c20b7189
--- /dev/null
+++ b/src/shader_recompiler/ir_opt/position_pass.cpp
@@ -0,0 +1,77 @@
+// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <boost/container/small_vector.hpp>
+
+#include "shader_recompiler/frontend/ir/basic_block.h"
+#include "shader_recompiler/frontend/ir/ir_emitter.h"
+#include "shader_recompiler/frontend/ir/value.h"
+#include "shader_recompiler/ir_opt/passes.h"
+
+namespace Shader::Optimization {
+
+namespace {
+struct PositionInst {
+ IR::Inst* inst;
+ IR::Block* block;
+ IR::Attribute attr;
+};
+using PositionInstVector = boost::container::small_vector<PositionInst, 24>;
+} // Anonymous namespace
+
+void PositionPass(Environment& env, IR::Program& program) {
+ if (env.ShaderStage() != Stage::VertexB || env.ReadViewportTransformState()) {
+ return;
+ }
+
+ Info& info{program.info};
+ info.uses_render_area = true;
+
+ PositionInstVector to_replace;
+ for (IR::Block* const block : program.post_order_blocks) {
+ for (IR::Inst& inst : block->Instructions()) {
+ switch (inst.GetOpcode()) {
+ case IR::Opcode::SetAttribute: {
+ const IR::Attribute attr{inst.Arg(0).Attribute()};
+ switch (attr) {
+ case IR::Attribute::PositionX:
+ case IR::Attribute::PositionY: {
+ to_replace.push_back(PositionInst{.inst = &inst, .block = block, .attr = attr});
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ for (PositionInst& position_inst : to_replace) {
+ IR::IREmitter ir{*position_inst.block,
+ IR::Block::InstructionList::s_iterator_to(*position_inst.inst)};
+ const IR::F32 value(position_inst.inst->Arg(1));
+ const IR::F32F64 scale(ir.Imm32(2.f));
+ const IR::F32 negative_one{ir.Imm32(-1.f)};
+ switch (position_inst.attr) {
+ case IR::Attribute::PositionX: {
+ position_inst.inst->SetArg(
+ 1,
+ ir.FPFma(value, ir.FPMul(ir.FPRecip(ir.RenderAreaWidth()), scale), negative_one));
+ break;
+ }
+ case IR::Attribute::PositionY: {
+ position_inst.inst->SetArg(
+ 1,
+ ir.FPFma(value, ir.FPMul(ir.FPRecip(ir.RenderAreaHeight()), scale), negative_one));
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+} // namespace Shader::Optimization
diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp
index e8be58357..f5c86fcb1 100644
--- a/src/shader_recompiler/ir_opt/texture_pass.cpp
+++ b/src/shader_recompiler/ir_opt/texture_pass.cpp
@@ -11,6 +11,7 @@
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/ir/breadth_first_search.h"
#include "shader_recompiler/frontend/ir/ir_emitter.h"
+#include "shader_recompiler/host_translate_info.h"
#include "shader_recompiler/ir_opt/passes.h"
#include "shader_recompiler/shader_info.h"
@@ -363,6 +364,14 @@ TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
return env.ReadTextureType(lhs_raw | rhs_raw);
}
+TexturePixelFormat ReadTexturePixelFormat(Environment& env, const ConstBufferAddr& cbuf) {
+ const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index};
+ const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset};
+ const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset)};
+ const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)};
+ return env.ReadTexturePixelFormat(lhs_raw | rhs_raw);
+}
+
class Descriptors {
public:
explicit Descriptors(TextureBufferDescriptors& texture_buffer_descriptors_,
@@ -451,9 +460,41 @@ void PatchImageSampleImplicitLod(IR::Block& block, IR::Inst& inst) {
ir.FPMul(IR::F32(ir.CompositeExtract(coord, 1)),
ir.FPRecip(ir.ConvertUToF(32, 32, ir.CompositeExtract(texture_size, 1))))));
}
+
+void PatchTexelFetch(IR::Block& block, IR::Inst& inst, TexturePixelFormat pixel_format) {
+ const auto it{IR::Block::InstructionList::s_iterator_to(inst)};
+ IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
+ auto get_max_value = [pixel_format]() -> float {
+ switch (pixel_format) {
+ case TexturePixelFormat::A8B8G8R8_SNORM:
+ case TexturePixelFormat::R8G8_SNORM:
+ case TexturePixelFormat::R8_SNORM:
+ return 1.f / std::numeric_limits<char>::max();
+ case TexturePixelFormat::R16G16B16A16_SNORM:
+ case TexturePixelFormat::R16G16_SNORM:
+ case TexturePixelFormat::R16_SNORM:
+ return 1.f / std::numeric_limits<short>::max();
+ default:
+ throw InvalidArgument("Invalid texture pixel format");
+ }
+ };
+
+ const IR::Value new_inst{&*block.PrependNewInst(it, inst)};
+ const IR::F32 x(ir.CompositeExtract(new_inst, 0));
+ const IR::F32 y(ir.CompositeExtract(new_inst, 1));
+ const IR::F32 z(ir.CompositeExtract(new_inst, 2));
+ const IR::F32 w(ir.CompositeExtract(new_inst, 3));
+ const IR::F16F32F64 max_value(ir.Imm32(get_max_value()));
+ const IR::Value converted =
+ ir.CompositeConstruct(ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(x)), max_value),
+ ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(y)), max_value),
+ ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(z)), max_value),
+ ir.FPMul(ir.ConvertSToF(32, 32, ir.BitCast<IR::S32>(w)), max_value));
+ inst.ReplaceUsesWith(converted);
+}
} // Anonymous namespace
-void TexturePass(Environment& env, IR::Program& program) {
+void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo& host_info) {
TextureInstVector to_replace;
for (IR::Block* const block : program.post_order_blocks) {
for (IR::Inst& inst : block->Instructions()) {
@@ -597,6 +638,14 @@ void TexturePass(Environment& env, IR::Program& program) {
} else {
inst->SetArg(0, IR::Value{});
}
+
+ if (!host_info.support_snorm_render_buffer && inst->GetOpcode() == IR::Opcode::ImageFetch &&
+ flags.type == TextureType::Buffer) {
+ const auto pixel_format = ReadTexturePixelFormat(env, cbuf);
+ if (pixel_format != TexturePixelFormat::OTHER) {
+ PatchTexelFetch(*texture_inst.block, *texture_inst.inst, pixel_format);
+ }
+ }
}
}
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h
index 81097bf1a..ee6252bb5 100644
--- a/src/shader_recompiler/shader_info.h
+++ b/src/shader_recompiler/shader_info.h
@@ -29,6 +29,16 @@ enum class TextureType : u32 {
};
constexpr u32 NUM_TEXTURE_TYPES = 9;
+enum class TexturePixelFormat : u32 {
+ A8B8G8R8_SNORM,
+ R8_SNORM,
+ R8G8_SNORM,
+ R16G16B16A16_SNORM,
+ R16G16_SNORM,
+ R16_SNORM,
+ OTHER
+};
+
enum class ImageFormat : u32 {
Typeless,
R8_UINT,
@@ -117,6 +127,7 @@ struct Info {
bool uses_workgroup_id{};
bool uses_local_invocation_id{};
bool uses_invocation_id{};
+ bool uses_invocation_info{};
bool uses_sample_id{};
bool uses_is_helper_invocation{};
bool uses_subgroup_invocation_id{};
@@ -182,6 +193,7 @@ struct Info {
bool uses_shadow_lod{};
bool uses_rescaling_uniform{};
bool uses_cbuf_indirect{};
+ bool uses_render_area{};
IR::Type used_constant_buffer_types{};
IR::Type used_storage_buffer_types{};
diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp
index 71121e42a..f7236afab 100644
--- a/src/tests/video_core/buffer_base.cpp
+++ b/src/tests/video_core/buffer_base.cpp
@@ -44,7 +44,7 @@ public:
[[nodiscard]] unsigned Count() const noexcept {
unsigned count = 0;
- for (const auto [index, value] : page_table) {
+ for (const auto& [index, value] : page_table) {
count += value;
}
return count;
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 106991969..d7f7d336c 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -73,8 +73,6 @@ add_library(video_core STATIC
macro/macro_hle.h
macro/macro_interpreter.cpp
macro/macro_interpreter.h
- macro/macro_jit_x64.cpp
- macro/macro_jit_x64.h
fence_manager.h
gpu.cpp
gpu.h
@@ -245,7 +243,7 @@ add_library(video_core STATIC
create_target_directory_groups(video_core)
target_link_libraries(video_core PUBLIC common core)
-target_link_libraries(video_core PUBLIC glad shader_recompiler xbyak)
+target_link_libraries(video_core PUBLIC glad shader_recompiler)
if (YUZU_USE_BUNDLED_FFMPEG AND NOT WIN32)
add_dependencies(video_core ffmpeg-build)
@@ -282,8 +280,19 @@ else()
-Wno-sign-conversion
)
+
+ # xbyak
+ set_source_files_properties(macro/macro_jit_x64.cpp PROPERTIES COMPILE_OPTIONS "-Wno-conversion;-Wno-shadow")
endif()
if (ARCHITECTURE_x86_64)
+ target_sources(video_core PRIVATE
+ macro/macro_jit_x64.cpp
+ macro/macro_jit_x64.h
+ )
+ target_link_libraries(video_core PUBLIC xbyak)
+endif()
+
+if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
target_link_libraries(video_core PRIVATE dynarmic)
endif()
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index b1a22b76c..4a2f2c1fd 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -117,10 +117,15 @@ void Maxwell3D::InitializeRegisterDefaults() {
shadow_state = regs;
- mme_inline[MAXWELL3D_REG_INDEX(draw.end)] = true;
- mme_inline[MAXWELL3D_REG_INDEX(draw.begin)] = true;
- mme_inline[MAXWELL3D_REG_INDEX(vertex_buffer.count)] = true;
- mme_inline[MAXWELL3D_REG_INDEX(index_buffer.count)] = true;
+ draw_command[MAXWELL3D_REG_INDEX(draw.end)] = true;
+ draw_command[MAXWELL3D_REG_INDEX(draw.begin)] = true;
+ draw_command[MAXWELL3D_REG_INDEX(vertex_buffer.first)] = true;
+ draw_command[MAXWELL3D_REG_INDEX(vertex_buffer.count)] = true;
+ draw_command[MAXWELL3D_REG_INDEX(index_buffer.first)] = true;
+ draw_command[MAXWELL3D_REG_INDEX(index_buffer.count)] = true;
+ draw_command[MAXWELL3D_REG_INDEX(draw_inline_index)] = true;
+ draw_command[MAXWELL3D_REG_INDEX(inline_index_2x16.even)] = true;
+ draw_command[MAXWELL3D_REG_INDEX(inline_index_4x8.index0)] = true;
}
void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool is_last_call) {
@@ -208,25 +213,21 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
return ProcessCBBind(3);
case MAXWELL3D_REG_INDEX(bind_groups[4].raw_config):
return ProcessCBBind(4);
- case MAXWELL3D_REG_INDEX(draw.end):
- return DrawArrays();
case MAXWELL3D_REG_INDEX(index_buffer32_first):
regs.index_buffer.count = regs.index_buffer32_first.count;
regs.index_buffer.first = regs.index_buffer32_first.first;
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
- return DrawArrays();
+ return ProcessDraw();
case MAXWELL3D_REG_INDEX(index_buffer16_first):
regs.index_buffer.count = regs.index_buffer16_first.count;
regs.index_buffer.first = regs.index_buffer16_first.first;
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
- return DrawArrays();
+ return ProcessDraw();
case MAXWELL3D_REG_INDEX(index_buffer8_first):
regs.index_buffer.count = regs.index_buffer8_first.count;
regs.index_buffer.first = regs.index_buffer8_first.first;
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
- // a macro calls this one over and over, should it increase instancing?
- // Used by Hades and likely other Vulkan games.
- return DrawArrays();
+ return ProcessDraw();
case MAXWELL3D_REG_INDEX(topology_override):
use_topology_override = true;
return;
@@ -261,14 +262,13 @@ void Maxwell3D::CallMacroMethod(u32 method, const std::vector<u32>& parameters)
// Execute the current macro.
macro_engine->Execute(macro_positions[entry], parameters);
- if (mme_draw.current_mode != MMEDrawMode::Undefined) {
- FlushMMEInlineDraw();
- }
+
+ ProcessDeferredDraw();
}
void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
- // It is an error to write to a register other than the current macro's ARG register before it
- // has finished execution.
+ // It is an error to write to a register other than the current macro's ARG register before
+ // it has finished execution.
if (executing_macro != 0) {
ASSERT(method == executing_macro + 1);
}
@@ -283,9 +283,33 @@ void Maxwell3D::CallMethod(u32 method, u32 method_argument, bool is_last_call) {
ASSERT_MSG(method < Regs::NUM_REGS,
"Invalid Maxwell3D register, increase the size of the Regs structure");
- const u32 argument = ProcessShadowRam(method, method_argument);
- ProcessDirtyRegisters(method, argument);
- ProcessMethodCall(method, argument, method_argument, is_last_call);
+ if (draw_command[method]) {
+ regs.reg_array[method] = method_argument;
+ deferred_draw_method.push_back(method);
+ auto u32_to_u8 = [&](const u32 argument) {
+ inline_index_draw_indexes.push_back(static_cast<u8>(argument & 0x000000ff));
+ inline_index_draw_indexes.push_back(static_cast<u8>((argument & 0x0000ff00) >> 8));
+ inline_index_draw_indexes.push_back(static_cast<u8>((argument & 0x00ff0000) >> 16));
+ inline_index_draw_indexes.push_back(static_cast<u8>((argument & 0xff000000) >> 24));
+ };
+ if (MAXWELL3D_REG_INDEX(draw_inline_index) == method) {
+ u32_to_u8(method_argument);
+ } else if (MAXWELL3D_REG_INDEX(inline_index_2x16.even) == method) {
+ u32_to_u8(regs.inline_index_2x16.even);
+ u32_to_u8(regs.inline_index_2x16.odd);
+ } else if (MAXWELL3D_REG_INDEX(inline_index_4x8.index0) == method) {
+ u32_to_u8(regs.inline_index_4x8.index0);
+ u32_to_u8(regs.inline_index_4x8.index1);
+ u32_to_u8(regs.inline_index_4x8.index2);
+ u32_to_u8(regs.inline_index_4x8.index3);
+ }
+ } else {
+ ProcessDeferredDraw();
+
+ const u32 argument = ProcessShadowRam(method, method_argument);
+ ProcessDirtyRegisters(method, argument);
+ ProcessMethodCall(method, argument, method_argument, is_last_call);
+ }
}
void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
@@ -326,55 +350,6 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
}
}
-void Maxwell3D::StepInstance(const MMEDrawMode expected_mode, const u32 count) {
- if (mme_draw.current_mode == MMEDrawMode::Undefined) {
- if (mme_draw.gl_begin_consume) {
- mme_draw.current_mode = expected_mode;
- mme_draw.current_count = count;
- mme_draw.instance_count = 1;
- mme_draw.gl_begin_consume = false;
- mme_draw.gl_end_count = 0;
- }
- return;
- } else {
- if (mme_draw.current_mode == expected_mode && count == mme_draw.current_count &&
- mme_draw.instance_mode && mme_draw.gl_begin_consume) {
- mme_draw.instance_count++;
- mme_draw.gl_begin_consume = false;
- return;
- } else {
- FlushMMEInlineDraw();
- }
- }
- // Tail call in case it needs to retry.
- StepInstance(expected_mode, count);
-}
-
-void Maxwell3D::CallMethodFromMME(u32 method, u32 method_argument) {
- if (mme_inline[method]) {
- regs.reg_array[method] = method_argument;
- if (method == MAXWELL3D_REG_INDEX(vertex_buffer.count) ||
- method == MAXWELL3D_REG_INDEX(index_buffer.count)) {
- const MMEDrawMode expected_mode = method == MAXWELL3D_REG_INDEX(vertex_buffer.count)
- ? MMEDrawMode::Array
- : MMEDrawMode::Indexed;
- StepInstance(expected_mode, method_argument);
- } else if (method == MAXWELL3D_REG_INDEX(draw.begin)) {
- mme_draw.instance_mode =
- (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) ||
- (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Unchanged);
- mme_draw.gl_begin_consume = true;
- } else {
- mme_draw.gl_end_count++;
- }
- } else {
- if (mme_draw.current_mode != MMEDrawMode::Undefined) {
- FlushMMEInlineDraw();
- }
- CallMethod(method, method_argument, true);
- }
-}
-
void Maxwell3D::ProcessTopologyOverride() {
using PrimitiveTopology = Maxwell3D::Regs::PrimitiveTopology;
using PrimitiveTopologyOverride = Maxwell3D::Regs::PrimitiveTopologyOverride;
@@ -404,41 +379,6 @@ void Maxwell3D::ProcessTopologyOverride() {
}
}
-void Maxwell3D::FlushMMEInlineDraw() {
- LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(),
- regs.vertex_buffer.count);
- ASSERT_MSG(!(regs.index_buffer.count && regs.vertex_buffer.count), "Both indexed and direct?");
- ASSERT(mme_draw.instance_count == mme_draw.gl_end_count);
-
- // Both instance configuration registers can not be set at the same time.
- ASSERT_MSG(regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First ||
- regs.draw.instance_id != Maxwell3D::Regs::Draw::InstanceId::Unchanged,
- "Illegal combination of instancing parameters");
-
- ProcessTopologyOverride();
-
- const bool is_indexed = mme_draw.current_mode == MMEDrawMode::Indexed;
- if (ShouldExecute()) {
- rasterizer->Draw(is_indexed, true);
- }
-
- // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
- // the game is trying to draw indexed or direct mode. This needs to be verified on HW still -
- // it's possible that it is incorrect and that there is some other register used to specify the
- // drawing mode.
- if (is_indexed) {
- regs.index_buffer.count = 0;
- } else {
- regs.vertex_buffer.count = 0;
- }
- mme_draw.current_mode = MMEDrawMode::Undefined;
- mme_draw.current_count = 0;
- mme_draw.instance_count = 0;
- mme_draw.instance_mode = false;
- mme_draw.gl_begin_consume = false;
- mme_draw.gl_end_count = 0;
-}
-
void Maxwell3D::ProcessMacroUpload(u32 data) {
macro_engine->AddCode(regs.load_mme.instruction_ptr++, data);
}
@@ -573,42 +513,6 @@ void Maxwell3D::ProcessSyncPoint() {
rasterizer->SignalSyncPoint(sync_point);
}
-void Maxwell3D::DrawArrays() {
- LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(),
- regs.vertex_buffer.count);
- ASSERT_MSG(!(regs.index_buffer.count && regs.vertex_buffer.count), "Both indexed and direct?");
-
- // Both instance configuration registers can not be set at the same time.
- ASSERT_MSG(regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First ||
- regs.draw.instance_id != Maxwell3D::Regs::Draw::InstanceId::Unchanged,
- "Illegal combination of instancing parameters");
-
- ProcessTopologyOverride();
-
- if (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) {
- // Increment the current instance *before* drawing.
- state.current_instance++;
- } else if (regs.draw.instance_id != Maxwell3D::Regs::Draw::InstanceId::Unchanged) {
- // Reset the current instance to 0.
- state.current_instance = 0;
- }
-
- const bool is_indexed{regs.index_buffer.count && !regs.vertex_buffer.count};
- if (ShouldExecute()) {
- rasterizer->Draw(is_indexed, false);
- }
-
- // TODO(bunnei): Below, we reset vertex count so that we can use these registers to determine if
- // the game is trying to draw indexed or direct mode. This needs to be verified on HW still -
- // it's possible that it is incorrect and that there is some other register used to specify the
- // drawing mode.
- if (is_indexed) {
- regs.index_buffer.count = 0;
- } else {
- regs.vertex_buffer.count = 0;
- }
-}
-
std::optional<u64> Maxwell3D::GetQueryResult() {
switch (regs.report_semaphore.query.report) {
case Regs::ReportSemaphore::Report::Payload:
@@ -691,4 +595,83 @@ void Maxwell3D::ProcessClearBuffers() {
rasterizer->Clear();
}
+void Maxwell3D::ProcessDraw(u32 instance_count) {
+ LOG_TRACE(HW_GPU, "called, topology={}, count={}", regs.draw.topology.Value(),
+ regs.vertex_buffer.count);
+
+ ASSERT_MSG(!(regs.index_buffer.count && regs.vertex_buffer.count), "Both indexed and direct?");
+
+ // Both instance configuration registers can not be set at the same time.
+ ASSERT_MSG(regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::First ||
+ regs.draw.instance_id != Maxwell3D::Regs::Draw::InstanceId::Unchanged,
+ "Illegal combination of instancing parameters");
+
+ ProcessTopologyOverride();
+
+ const bool is_indexed = regs.index_buffer.count && !regs.vertex_buffer.count;
+ if (ShouldExecute()) {
+ rasterizer->Draw(is_indexed, instance_count);
+ }
+
+ if (is_indexed) {
+ regs.index_buffer.count = 0;
+ } else {
+ regs.vertex_buffer.count = 0;
+ }
+}
+
+void Maxwell3D::ProcessDeferredDraw() {
+ if (deferred_draw_method.empty()) {
+ return;
+ }
+
+ enum class DrawMode {
+ Undefined,
+ General,
+ Instance,
+ };
+ DrawMode draw_mode{DrawMode::Undefined};
+ u32 method_count = static_cast<u32>(deferred_draw_method.size());
+ u32 method = deferred_draw_method[method_count - 1];
+ if (MAXWELL3D_REG_INDEX(draw.end) != method) {
+ return;
+ }
+ draw_mode = (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Subsequent) ||
+ (regs.draw.instance_id == Maxwell3D::Regs::Draw::InstanceId::Unchanged)
+ ? DrawMode::Instance
+ : DrawMode::General;
+ u32 instance_count = 0;
+ if (draw_mode == DrawMode::Instance) {
+ u32 vertex_buffer_count = 0;
+ u32 index_buffer_count = 0;
+ for (u32 index = 0; index < method_count; ++index) {
+ method = deferred_draw_method[index];
+ if (method == MAXWELL3D_REG_INDEX(vertex_buffer.count)) {
+ instance_count = ++vertex_buffer_count;
+ } else if (method == MAXWELL3D_REG_INDEX(index_buffer.count)) {
+ instance_count = ++index_buffer_count;
+ }
+ }
+ ASSERT_MSG(!(vertex_buffer_count && index_buffer_count),
+ "Instance both indexed and direct?");
+ } else {
+ instance_count = 1;
+ for (u32 index = 0; index < method_count; ++index) {
+ method = deferred_draw_method[index];
+ if (MAXWELL3D_REG_INDEX(draw_inline_index) == method ||
+ MAXWELL3D_REG_INDEX(inline_index_2x16.even) == method ||
+ MAXWELL3D_REG_INDEX(inline_index_4x8.index0) == method) {
+ regs.index_buffer.count = static_cast<u32>(inline_index_draw_indexes.size() / 4);
+ regs.index_buffer.format = Regs::IndexFormat::UnsignedInt;
+ break;
+ }
+ }
+ }
+
+ ProcessDraw(instance_count);
+
+ deferred_draw_method.clear();
+ inline_index_draw_indexes.clear();
+}
+
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 75e3b868d..910ab213a 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -1739,14 +1739,11 @@ public:
Footprint_1x1_Virtual = 2,
};
- struct InlineIndex4x8Align {
+ struct InlineIndex4x8 {
union {
BitField<0, 30, u32> count;
BitField<30, 2, u32> start;
};
- };
-
- struct InlineIndex4x8Index {
union {
BitField<0, 8, u32> index0;
BitField<8, 8, u32> index1;
@@ -2836,8 +2833,7 @@ public:
u32 depth_write_enabled; ///< 0x12E8
u32 alpha_test_enabled; ///< 0x12EC
INSERT_PADDING_BYTES_NOINIT(0x10);
- InlineIndex4x8Align inline_index_4x8_align; ///< 0x1300
- InlineIndex4x8Index inline_index_4x8_index; ///< 0x1304
+ InlineIndex4x8 inline_index_4x8; ///< 0x1300
D3DCullMode d3d_cull_mode; ///< 0x1308
ComparisonOp depth_test_func; ///< 0x130C
f32 alpha_test_ref; ///< 0x1310
@@ -2974,7 +2970,7 @@ public:
CullFace gl_cull_face; ///< 0x1920
Viewport::PixelCenter viewport_pixel_center; ///< 0x1924
INSERT_PADDING_BYTES_NOINIT(0x4);
- u32 viewport_scale_offset_enbled; ///< 0x192C
+ u32 viewport_scale_offset_enabled; ///< 0x192C
INSERT_PADDING_BYTES_NOINIT(0xC);
ViewportClipControl viewport_clip_control; ///< 0x193C
UserClip::Op user_clip_op; ///< 0x1940
@@ -3048,8 +3044,6 @@ public:
};
std::array<ShaderStageInfo, Regs::MaxShaderStage> shader_stages;
-
- u32 current_instance = 0; ///< Current instance to be used to simulate instanced rendering.
};
State state{};
@@ -3064,11 +3058,6 @@ public:
void CallMultiMethod(u32 method, const u32* base_start, u32 amount,
u32 methods_pending) override;
- /// Write the value to the register identified by method.
- void CallMethodFromMME(u32 method, u32 method_argument);
-
- void FlushMMEInlineDraw();
-
bool ShouldExecute() const {
return execute_on;
}
@@ -3081,21 +3070,6 @@ public:
return *rasterizer;
}
- enum class MMEDrawMode : u32 {
- Undefined,
- Array,
- Indexed,
- };
-
- struct MMEDrawState {
- MMEDrawMode current_mode{MMEDrawMode::Undefined};
- u32 current_count{};
- u32 instance_count{};
- bool instance_mode{};
- bool gl_begin_consume{};
- u32 gl_end_count{};
- } mme_draw;
-
struct DirtyState {
using Flags = std::bitset<std::numeric_limits<u8>::max()>;
using Table = std::array<u8, Regs::NUM_REGS>;
@@ -3105,6 +3079,8 @@ public:
Tables tables{};
} dirty;
+ std::vector<u8> inline_index_draw_indexes;
+
private:
void InitializeRegisterDefaults();
@@ -3164,14 +3140,12 @@ private:
/// Handles a write to the CB_BIND register.
void ProcessCBBind(size_t stage_index);
- /// Handles a write to the VERTEX_END_GL register, triggering a draw.
- void DrawArrays();
-
/// Handles use of topology overrides (e.g., to avoid using a topology assigned from a macro)
void ProcessTopologyOverride();
- // Handles a instance drawcall from MME
- void StepInstance(MMEDrawMode expected_mode, u32 count);
+ void ProcessDraw(u32 instance_count = 1);
+
+ void ProcessDeferredDraw();
/// Returns a query's value or an empty object if the value will be deferred through a cache.
std::optional<u64> GetQueryResult();
@@ -3184,8 +3158,6 @@ private:
/// Start offsets of each macro in macro_memory
std::array<u32, 0x80> macro_positions{};
- std::array<bool, Regs::NUM_REGS> mme_inline{};
-
/// Macro method that is currently being executed / being fed parameters.
u32 executing_macro = 0;
/// Parameters that have been submitted to the macro call so far.
@@ -3198,6 +3170,9 @@ private:
bool execute_on{true};
bool use_topology_override{false};
+
+ std::array<bool, Regs::NUM_REGS> draw_command{};
+ std::vector<u32> deferred_draw_method;
};
#define ASSERT_REG_POSITION(field_name, position) \
@@ -3402,8 +3377,7 @@ ASSERT_REG_POSITION(alpha_to_coverage_dither, 0x12E0);
ASSERT_REG_POSITION(blend_per_target_enabled, 0x12E4);
ASSERT_REG_POSITION(depth_write_enabled, 0x12E8);
ASSERT_REG_POSITION(alpha_test_enabled, 0x12EC);
-ASSERT_REG_POSITION(inline_index_4x8_align, 0x1300);
-ASSERT_REG_POSITION(inline_index_4x8_index, 0x1304);
+ASSERT_REG_POSITION(inline_index_4x8, 0x1300);
ASSERT_REG_POSITION(d3d_cull_mode, 0x1308);
ASSERT_REG_POSITION(depth_test_func, 0x130C);
ASSERT_REG_POSITION(alpha_test_ref, 0x1310);
@@ -3508,7 +3482,7 @@ ASSERT_REG_POSITION(gl_cull_test_enabled, 0x1918);
ASSERT_REG_POSITION(gl_front_face, 0x191C);
ASSERT_REG_POSITION(gl_cull_face, 0x1920);
ASSERT_REG_POSITION(viewport_pixel_center, 0x1924);
-ASSERT_REG_POSITION(viewport_scale_offset_enbled, 0x192C);
+ASSERT_REG_POSITION(viewport_scale_offset_enabled, 0x192C);
ASSERT_REG_POSITION(viewport_clip_control, 0x193C);
ASSERT_REG_POSITION(user_clip_op, 0x1940);
ASSERT_REG_POSITION(render_enable_override, 0x1944);
diff --git a/src/video_core/macro/macro.cpp b/src/video_core/macro/macro.cpp
index f61d5998e..505d81c1e 100644
--- a/src/video_core/macro/macro.cpp
+++ b/src/video_core/macro/macro.cpp
@@ -16,7 +16,10 @@
#include "video_core/macro/macro.h"
#include "video_core/macro/macro_hle.h"
#include "video_core/macro/macro_interpreter.h"
+
+#ifdef ARCHITECTURE_x86_64
#include "video_core/macro/macro_jit_x64.h"
+#endif
namespace Tegra {
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
index 8a8adbb42..f896591bf 100644
--- a/src/video_core/macro/macro_hle.cpp
+++ b/src/video_core/macro/macro_hle.cpp
@@ -22,35 +22,29 @@ void HLE_771BB18C62444DA0(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.regs.draw.topology.Assign(
static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[0] & 0x3ffffff));
maxwell3d.regs.global_base_instance_index = parameters[5];
- maxwell3d.mme_draw.instance_count = instance_count;
maxwell3d.regs.global_base_vertex_index = parameters[3];
maxwell3d.regs.index_buffer.count = parameters[1];
maxwell3d.regs.index_buffer.first = parameters[4];
if (maxwell3d.ShouldExecute()) {
- maxwell3d.Rasterizer().Draw(true, true);
+ maxwell3d.Rasterizer().Draw(true, instance_count);
}
maxwell3d.regs.index_buffer.count = 0;
- maxwell3d.mme_draw.instance_count = 0;
- maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
}
void HLE_0D61FC9FAAC9FCAD(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
- const u32 count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]);
+ const u32 instance_count = (maxwell3d.GetRegisterValue(0xD1B) & parameters[2]);
maxwell3d.regs.vertex_buffer.first = parameters[3];
maxwell3d.regs.vertex_buffer.count = parameters[1];
maxwell3d.regs.global_base_instance_index = parameters[4];
maxwell3d.regs.draw.topology.Assign(
static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[0]));
- maxwell3d.mme_draw.instance_count = count;
if (maxwell3d.ShouldExecute()) {
- maxwell3d.Rasterizer().Draw(false, true);
+ maxwell3d.Rasterizer().Draw(false, instance_count);
}
maxwell3d.regs.vertex_buffer.count = 0;
- maxwell3d.mme_draw.instance_count = 0;
- maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
}
void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
@@ -63,24 +57,21 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
maxwell3d.regs.global_base_vertex_index = element_base;
maxwell3d.regs.global_base_instance_index = base_instance;
- maxwell3d.mme_draw.instance_count = instance_count;
- maxwell3d.CallMethodFromMME(0x8e3, 0x640);
- maxwell3d.CallMethodFromMME(0x8e4, element_base);
- maxwell3d.CallMethodFromMME(0x8e5, base_instance);
+ maxwell3d.CallMethod(0x8e3, 0x640, true);
+ maxwell3d.CallMethod(0x8e4, element_base, true);
+ maxwell3d.CallMethod(0x8e5, base_instance, true);
maxwell3d.regs.draw.topology.Assign(
static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[0]));
if (maxwell3d.ShouldExecute()) {
- maxwell3d.Rasterizer().Draw(true, true);
+ maxwell3d.Rasterizer().Draw(true, instance_count);
}
maxwell3d.regs.vertex_id_base = 0x0;
maxwell3d.regs.index_buffer.count = 0;
maxwell3d.regs.global_base_vertex_index = 0x0;
maxwell3d.regs.global_base_instance_index = 0x0;
- maxwell3d.mme_draw.instance_count = 0;
- maxwell3d.CallMethodFromMME(0x8e3, 0x640);
- maxwell3d.CallMethodFromMME(0x8e4, 0x0);
- maxwell3d.CallMethodFromMME(0x8e5, 0x0);
- maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
+ maxwell3d.CallMethod(0x8e3, 0x640, true);
+ maxwell3d.CallMethod(0x8e4, 0x0, true);
+ maxwell3d.CallMethod(0x8e5, 0x0, true);
}
// Multidraw Indirect
@@ -91,11 +82,9 @@ void HLE_3F5E74B9C9A50164(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.regs.index_buffer.count = 0;
maxwell3d.regs.global_base_vertex_index = 0x0;
maxwell3d.regs.global_base_instance_index = 0x0;
- maxwell3d.mme_draw.instance_count = 0;
- maxwell3d.CallMethodFromMME(0x8e3, 0x640);
- maxwell3d.CallMethodFromMME(0x8e4, 0x0);
- maxwell3d.CallMethodFromMME(0x8e5, 0x0);
- maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
+ maxwell3d.CallMethod(0x8e3, 0x640, true);
+ maxwell3d.CallMethod(0x8e4, 0x0, true);
+ maxwell3d.CallMethod(0x8e5, 0x0, true);
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
});
const u32 start_indirect = parameters[0];
@@ -127,15 +116,13 @@ void HLE_3F5E74B9C9A50164(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
maxwell3d.regs.index_buffer.count = num_vertices;
maxwell3d.regs.global_base_vertex_index = base_vertex;
maxwell3d.regs.global_base_instance_index = base_instance;
- maxwell3d.mme_draw.instance_count = instance_count;
- maxwell3d.CallMethodFromMME(0x8e3, 0x640);
- maxwell3d.CallMethodFromMME(0x8e4, base_vertex);
- maxwell3d.CallMethodFromMME(0x8e5, base_instance);
+ maxwell3d.CallMethod(0x8e3, 0x640, true);
+ maxwell3d.CallMethod(0x8e4, base_vertex, true);
+ maxwell3d.CallMethod(0x8e5, base_instance, true);
maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
if (maxwell3d.ShouldExecute()) {
- maxwell3d.Rasterizer().Draw(true, true);
+ maxwell3d.Rasterizer().Draw(true, instance_count);
}
- maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
}
}
diff --git a/src/video_core/macro/macro_interpreter.cpp b/src/video_core/macro/macro_interpreter.cpp
index f670b1bca..c0d32c112 100644
--- a/src/video_core/macro/macro_interpreter.cpp
+++ b/src/video_core/macro/macro_interpreter.cpp
@@ -335,7 +335,7 @@ void MacroInterpreterImpl::SetMethodAddress(u32 address) {
}
void MacroInterpreterImpl::Send(u32 value) {
- maxwell3d.CallMethodFromMME(method_address.address, value);
+ maxwell3d.CallMethod(method_address.address, value, true);
// Increment the method address by the method increment.
method_address.address.Assign(method_address.address.Value() +
method_address.increment.Value());
diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp
index a302a9603..25c1ce798 100644
--- a/src/video_core/macro/macro_jit_x64.cpp
+++ b/src/video_core/macro/macro_jit_x64.cpp
@@ -346,7 +346,7 @@ void MacroJITx64Impl::Compile_Read(Macro::Opcode opcode) {
}
void Send(Engines::Maxwell3D* maxwell3d, Macro::MethodAddress method_address, u32 value) {
- maxwell3d->CallMethodFromMME(method_address.address, value);
+ maxwell3d->CallMethod(method_address.address, value, true);
}
void MacroJITx64Impl::Compile_Send(Xbyak::Reg32 value) {
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 384350dbd..8c8dfcca6 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -45,7 +45,7 @@ MemoryManager::MemoryManager(Core::System& system_, u64 address_space_bits_, u64
kind_valus.fill(PTEKind::INVALID);
big_kinds.resize(big_page_table_size / 32, kind_valus);
entries.resize(page_table_size / 32, 0);
- kinds.resize(big_page_table_size / 32, kind_valus);
+ kinds.resize(page_table_size / 32, kind_valus);
}
MemoryManager::~MemoryManager() = default;
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index d2d40884c..1cbfef090 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -40,7 +40,7 @@ public:
virtual ~RasterizerInterface() = default;
/// Dispatches a draw invocation
- virtual void Draw(bool is_indexed, bool is_instanced) = 0;
+ virtual void Draw(bool is_indexed, u32 instance_count) = 0;
/// Clear the current framebuffer
virtual void Clear() = 0;
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 08f4d69ab..6af4ae793 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -29,17 +29,17 @@ constexpr std::array PROGRAM_LUT{
[[nodiscard]] GLenum GetTextureBufferFormat(GLenum gl_format) {
switch (gl_format) {
case GL_RGBA8_SNORM:
- return GL_RGBA8;
+ return GL_RGBA8I;
case GL_R8_SNORM:
- return GL_R8;
+ return GL_R8I;
case GL_RGBA16_SNORM:
- return GL_RGBA16;
+ return GL_RGBA16I;
case GL_R16_SNORM:
- return GL_R16;
+ return GL_R16I;
case GL_RG16_SNORM:
- return GL_RG16;
+ return GL_RG16I;
case GL_RG8_SNORM:
- return GL_RG8;
+ return GL_RG8I;
default:
return gl_format;
}
@@ -96,9 +96,6 @@ GLuint Buffer::View(u32 offset, u32 size, PixelFormat format) {
texture.Create(GL_TEXTURE_BUFFER);
const GLenum gl_format{MaxwellToGL::GetFormatTuple(format).internal_format};
const GLenum texture_format{GetTextureBufferFormat(gl_format)};
- if (texture_format != gl_format) {
- LOG_WARNING(Render_OpenGL, "Emulating SNORM texture buffer with UNORM.");
- }
glTextureBufferRange(texture.handle, texture_format, buffer.handle, offset, size);
views.push_back({
.offset = offset,
diff --git a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
index 1d20a79ec..c115dabe1 100644
--- a/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
+++ b/src/video_core/renderer_opengl/gl_graphics_pipeline.cpp
@@ -503,6 +503,17 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
float_image_scaling_mask, down_factor, 0.0f);
}
}
+ if (info.uses_render_area) {
+ const auto render_area_width(static_cast<GLfloat>(regs.surface_clip.width));
+ const auto render_area_height(static_cast<GLfloat>(regs.surface_clip.height));
+ if (use_assembly) {
+ glProgramLocalParameter4fARB(AssemblyStage(stage), 1, render_area_width,
+ render_area_height, 0.0f, 0.0f);
+ } else {
+ glProgramUniform4f(source_programs[stage].handle, 1, render_area_width,
+ render_area_height, 0.0f, 0.0f);
+ }
+ }
}};
if constexpr (Spec::enabled_stages[0]) {
prepare_stage(0);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index e5c09a969..8a8b5ce54 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -205,7 +205,7 @@ void RasterizerOpenGL::Clear() {
++num_queued_commands;
}
-void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
+void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) {
MICROPROFILE_SCOPE(OpenGL_Drawing);
SCOPE_EXIT({ gpu.TickWork(); });
@@ -222,14 +222,15 @@ void RasterizerOpenGL::Draw(bool is_indexed, bool is_instanced) {
pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed);
+ BindInlineIndexBuffer();
+
SyncState();
const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d->regs.draw.topology);
BeginTransformFeedback(pipeline, primitive_mode);
const GLuint base_instance = static_cast<GLuint>(maxwell3d->regs.global_base_instance_index);
- const GLsizei num_instances =
- static_cast<GLsizei>(is_instanced ? maxwell3d->mme_draw.instance_count : 1);
+ const GLsizei num_instances = static_cast<GLsizei>(instance_count);
if (is_indexed) {
const GLint base_vertex = static_cast<GLint>(maxwell3d->regs.global_base_vertex_index);
const GLsizei num_vertices = static_cast<GLsizei>(maxwell3d->regs.index_buffer.count);
@@ -617,6 +618,16 @@ void RasterizerOpenGL::SyncViewport() {
}
flags[Dirty::Viewport0 + index] = false;
+ if (!regs.viewport_scale_offset_enabled) {
+ const auto x = static_cast<GLfloat>(regs.surface_clip.x);
+ const auto y = static_cast<GLfloat>(regs.surface_clip.y);
+ const auto width = static_cast<GLfloat>(regs.surface_clip.width);
+ const auto height = static_cast<GLfloat>(regs.surface_clip.height);
+ glViewportIndexedf(static_cast<GLuint>(index), x, y, width != 0.0f ? width : 1.0f,
+ height != 0.0f ? height : 1.0f);
+ continue;
+ }
+
const auto& src = regs.viewport_transform[index];
GLfloat x = conv(src.translate_x - src.scale_x);
GLfloat y = conv(src.translate_y - src.scale_y);
@@ -1129,6 +1140,16 @@ void RasterizerOpenGL::ReleaseChannel(s32 channel_id) {
query_cache.EraseChannel(channel_id);
}
+void RasterizerOpenGL::BindInlineIndexBuffer() {
+ if (maxwell3d->inline_index_draw_indexes.empty()) {
+ return;
+ }
+ const auto data_count = static_cast<u32>(maxwell3d->inline_index_draw_indexes.size());
+ auto buffer = Buffer(buffer_cache_runtime, *this, 0, data_count);
+ buffer.ImmediateUpload(0, maxwell3d->inline_index_draw_indexes);
+ buffer_cache_runtime.BindIndexBuffer(buffer, 0, data_count);
+}
+
AccelerateDMA::AccelerateDMA(BufferCache& buffer_cache_) : buffer_cache{buffer_cache_} {}
bool AccelerateDMA::BufferCopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount) {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 45131b785..793e0d608 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -68,7 +68,7 @@ public:
StateTracker& state_tracker_);
~RasterizerOpenGL() override;
- void Draw(bool is_indexed, bool is_instanced) override;
+ void Draw(bool is_indexed, u32 instance_count) override;
void Clear() override;
void DispatchCompute() override;
void ResetCounter(VideoCore::QueryType type) override;
@@ -199,6 +199,8 @@ private:
/// End a transform feedback
void EndTransformFeedback();
+ void BindInlineIndexBuffer();
+
Tegra::GPU& gpu;
const Device& device;
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index e94cfdb1a..3fe04a115 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -49,7 +49,7 @@ using VideoCommon::LoadPipelines;
using VideoCommon::SerializePipeline;
using Context = ShaderContext::Context;
-constexpr u32 CACHE_VERSION = 6;
+constexpr u32 CACHE_VERSION = 7;
template <typename Container>
auto MakeSpan(Container& container) {
@@ -76,7 +76,8 @@ Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key,
}
break;
case Shader::Stage::TessellationEval:
- info.tess_clockwise = key.tessellation_clockwise != 0;
+ // Flip the face, as OpenGL's drawing is flipped.
+ info.tess_clockwise = key.tessellation_clockwise == 0;
info.tess_primitive = [&key] {
switch (key.tessellation_primitive) {
case Maxwell::Tessellation::DomainType::Isolines:
@@ -218,6 +219,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
.support_float16 = false,
.support_int64 = device.HasShaderInt64(),
.needs_demote_reorder = device.IsAmd(),
+ .support_snorm_render_buffer = false,
} {
if (use_asynchronous_shaders) {
workers = CreateWorkers();
diff --git a/src/video_core/renderer_opengl/gl_state_tracker.cpp b/src/video_core/renderer_opengl/gl_state_tracker.cpp
index a359f96f1..d53b422ca 100644
--- a/src/video_core/renderer_opengl/gl_state_tracker.cpp
+++ b/src/video_core/renderer_opengl/gl_state_tracker.cpp
@@ -70,8 +70,8 @@ void SetupDirtyViewports(Tables& tables) {
FillBlock(tables[1], OFF(viewport_transform), NUM(viewport_transform), Viewports);
FillBlock(tables[1], OFF(viewports), NUM(viewports), Viewports);
- tables[0][OFF(viewport_scale_offset_enbled)] = ViewportTransform;
- tables[1][OFF(viewport_scale_offset_enbled)] = Viewports;
+ tables[0][OFF(viewport_scale_offset_enabled)] = ViewportTransform;
+ tables[1][OFF(viewport_scale_offset_enabled)] = Viewports;
}
void SetupDirtyScissors(Tables& tables) {
diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h
index b24f3424a..b7843e995 100644
--- a/src/video_core/renderer_vulkan/pipeline_helper.h
+++ b/src/video_core/renderer_vulkan/pipeline_helper.h
@@ -68,13 +68,15 @@ public:
}
vk::PipelineLayout CreatePipelineLayout(VkDescriptorSetLayout descriptor_set_layout) const {
+ using Shader::Backend::SPIRV::RenderAreaLayout;
using Shader::Backend::SPIRV::RescalingLayout;
const u32 size_offset = is_compute ? sizeof(RescalingLayout::down_factor) : 0u;
const VkPushConstantRange range{
.stageFlags = static_cast<VkShaderStageFlags>(
is_compute ? VK_SHADER_STAGE_COMPUTE_BIT : VK_SHADER_STAGE_ALL_GRAPHICS),
.offset = 0,
- .size = static_cast<u32>(sizeof(RescalingLayout)) - size_offset,
+ .size = static_cast<u32>(sizeof(RescalingLayout)) - size_offset +
+ static_cast<u32>(sizeof(RenderAreaLayout)),
};
return device->GetLogical().CreatePipelineLayout({
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
@@ -167,6 +169,12 @@ private:
u32 image_bit{1u};
};
+class RenderAreaPushConstant {
+public:
+ bool uses_render_area{};
+ std::array<f32, 4> words{};
+};
+
inline void PushImageDescriptors(TextureCache& texture_cache,
UpdateDescriptorQueue& update_descriptor_queue,
const Shader::Info& info, RescalingPushConstant& rescaling,
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index cb7fa2078..89426121f 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -480,11 +480,15 @@ void BlitScreen::RefreshResources(const Tegra::FramebufferConfig& framebuffer) {
fsr.reset();
}
- if (framebuffer.width == raw_width && framebuffer.height == raw_height && !raw_images.empty()) {
+ if (framebuffer.width == raw_width && framebuffer.height == raw_height &&
+ framebuffer.pixel_format == pixel_format && !raw_images.empty()) {
return;
}
+
raw_width = framebuffer.width;
raw_height = framebuffer.height;
+ pixel_format = framebuffer.pixel_format;
+
ReleaseRawImages();
CreateStagingBuffer(framebuffer);
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 29e2ea925..a2b73ec54 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -28,6 +28,10 @@ namespace VideoCore {
class RasterizerInterface;
}
+namespace Service::android {
+enum class PixelFormat : u32;
+}
+
namespace Vulkan {
struct ScreenInfo;
@@ -156,6 +160,7 @@ private:
u32 raw_width = 0;
u32 raw_height = 0;
+ Service::android::PixelFormat pixel_format{};
std::unique_ptr<FSR> fsr;
};
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index c3f66c8a3..1aa116cea 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -31,6 +31,7 @@ namespace {
using boost::container::small_vector;
using boost::container::static_vector;
using Shader::ImageBufferDescriptor;
+using Shader::Backend::SPIRV::RENDERAREA_LAYOUT_OFFSET;
using Shader::Backend::SPIRV::RESCALING_LAYOUT_DOWN_FACTOR_OFFSET;
using Shader::Backend::SPIRV::RESCALING_LAYOUT_WORDS_OFFSET;
using Tegra::Texture::TexturePair;
@@ -433,12 +434,19 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
update_descriptor_queue.Acquire();
RescalingPushConstant rescaling;
+ RenderAreaPushConstant render_area;
const VkSampler* samplers_it{samplers.data()};
const VideoCommon::ImageViewInOut* views_it{views.data()};
const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
buffer_cache.BindHostStageBuffers(stage);
PushImageDescriptors(texture_cache, update_descriptor_queue, stage_infos[stage], rescaling,
samplers_it, views_it);
+ const auto& info{stage_infos[0]};
+ if (info.uses_render_area) {
+ render_area.uses_render_area = true;
+ render_area.words = {static_cast<float>(regs.surface_clip.width),
+ static_cast<float>(regs.surface_clip.height)};
+ }
}};
if constexpr (Spec::enabled_stages[0]) {
prepare_stage(0);
@@ -455,10 +463,11 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
if constexpr (Spec::enabled_stages[4]) {
prepare_stage(4);
}
- ConfigureDraw(rescaling);
+ ConfigureDraw(rescaling, render_area);
}
-void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling) {
+void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
+ const RenderAreaPushConstant& render_area) {
texture_cache.UpdateRenderTargets(false);
scheduler.RequestRenderpass(texture_cache.GetFramebuffer());
@@ -474,7 +483,9 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling) {
const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)};
const void* const descriptor_data{update_descriptor_queue.UpdateData()};
scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(),
- is_rescaling, update_rescaling](vk::CommandBuffer cmdbuf) {
+ is_rescaling, update_rescaling,
+ uses_render_area = render_area.uses_render_area,
+ render_area_data = render_area.words](vk::CommandBuffer cmdbuf) {
if (bind_pipeline) {
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline);
}
@@ -488,6 +499,11 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling) {
RESCALING_LAYOUT_DOWN_FACTOR_OFFSET, sizeof(scale_down_factor),
&scale_down_factor);
}
+ if (uses_render_area) {
+ cmdbuf.PushConstants(*pipeline_layout, VK_SHADER_STAGE_ALL_GRAPHICS,
+ RENDERAREA_LAYOUT_OFFSET, sizeof(render_area_data),
+ &render_area_data);
+ }
if (!descriptor_set_layout) {
return;
}
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
index 85602592b..6bf577d25 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
@@ -62,6 +62,7 @@ class Device;
class PipelineStatistics;
class RenderPassCache;
class RescalingPushConstant;
+class RenderAreaPushConstant;
class Scheduler;
class UpdateDescriptorQueue;
@@ -119,7 +120,8 @@ private:
template <typename Spec>
void ConfigureImpl(bool is_indexed);
- void ConfigureDraw(const RescalingPushConstant& rescaling);
+ void ConfigureDraw(const RescalingPushConstant& rescaling,
+ const RenderAreaPushConstant& render_are);
void MakePipeline(VkRenderPass render_pass);
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 13d5a1f67..d4b0a542a 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -53,7 +53,7 @@ using VideoCommon::FileEnvironment;
using VideoCommon::GenericEnvironment;
using VideoCommon::GraphicsEnvironment;
-constexpr u32 CACHE_VERSION = 6;
+constexpr u32 CACHE_VERSION = 7;
template <typename Container>
auto MakeSpan(Container& container) {
@@ -166,6 +166,7 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program
}
break;
case Shader::Stage::TessellationEval:
+ info.tess_clockwise = key.state.tessellation_clockwise != 0;
info.tess_primitive = [&key] {
const u32 raw{key.state.tessellation_primitive.Value()};
switch (static_cast<Maxwell::Tessellation::DomainType>(raw)) {
@@ -325,6 +326,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
.support_int64 = device.IsShaderInt64Supported(),
.needs_demote_reorder = driver_id == VK_DRIVER_ID_AMD_PROPRIETARY_KHR ||
driver_id == VK_DRIVER_ID_AMD_OPEN_SOURCE_KHR,
+ .support_snorm_render_buffer = true,
};
}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 47dfb45a1..f69c0c50f 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -127,11 +127,10 @@ VkRect2D GetScissorState(const Maxwell& regs, size_t index, u32 up_scale = 1, u3
return scissor;
}
-DrawParams MakeDrawParams(const Maxwell& regs, u32 num_instances, bool is_instanced,
- bool is_indexed) {
+DrawParams MakeDrawParams(const Maxwell& regs, u32 num_instances, bool is_indexed) {
DrawParams params{
.base_instance = regs.global_base_instance_index,
- .num_instances = is_instanced ? num_instances : 1,
+ .num_instances = num_instances,
.base_vertex = is_indexed ? regs.global_base_vertex_index : regs.vertex_buffer.first,
.num_vertices = is_indexed ? regs.index_buffer.count : regs.vertex_buffer.count,
.first_index = is_indexed ? regs.index_buffer.first : 0,
@@ -157,12 +156,10 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
staging_pool(device, memory_allocator, scheduler), descriptor_pool(device, scheduler),
update_descriptor_queue(device, scheduler),
blit_image(device, scheduler, state_tracker, descriptor_pool),
- astc_decoder_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue,
- memory_allocator),
render_pass_cache(device), texture_cache_runtime{device, scheduler,
memory_allocator, staging_pool,
- blit_image, astc_decoder_pass,
- render_pass_cache},
+ blit_image, render_pass_cache,
+ descriptor_pool, update_descriptor_queue},
texture_cache(texture_cache_runtime, *this),
buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool,
update_descriptor_queue, descriptor_pool),
@@ -177,7 +174,7 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
RasterizerVulkan::~RasterizerVulkan() = default;
-void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
+void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
MICROPROFILE_SCOPE(Vulkan_Drawing);
SCOPE_EXIT({ gpu.TickWork(); });
@@ -194,13 +191,15 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
pipeline->SetEngine(maxwell3d, gpu_memory);
pipeline->Configure(is_indexed);
+ BindInlineIndexBuffer();
+
BeginTransformFeedback();
UpdateDynamicStates();
const auto& regs{maxwell3d->regs};
- const u32 num_instances{maxwell3d->mme_draw.instance_count};
- const DrawParams draw_params{MakeDrawParams(regs, num_instances, is_instanced, is_indexed)};
+ const u32 num_instances{instance_count};
+ const DrawParams draw_params{MakeDrawParams(regs, num_instances, is_indexed)};
scheduler.Record([draw_params](vk::CommandBuffer cmdbuf) {
if (draw_params.is_indexed) {
cmdbuf.DrawIndexed(draw_params.num_vertices, draw_params.num_instances,
@@ -304,14 +303,19 @@ void RasterizerVulkan::Clear() {
}
}
- scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) {
- const VkClearAttachment attachment{
- .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
- .colorAttachment = color_attachment,
- .clearValue = clear_value,
- };
- cmdbuf.ClearAttachments(attachment, clear_rect);
- });
+ if (regs.clear_surface.R && regs.clear_surface.G && regs.clear_surface.B &&
+ regs.clear_surface.A) {
+ scheduler.Record([color_attachment, clear_value, clear_rect](vk::CommandBuffer cmdbuf) {
+ const VkClearAttachment attachment{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .colorAttachment = color_attachment,
+ .clearValue = clear_value,
+ };
+ cmdbuf.ClearAttachments(attachment, clear_rect);
+ });
+ } else {
+ UNIMPLEMENTED_MSG("Unimplemented Clear only the specified channel");
+ }
}
if (!use_depth && !use_stencil) {
@@ -679,6 +683,22 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg
if (!state_tracker.TouchViewports()) {
return;
}
+ if (!regs.viewport_scale_offset_enabled) {
+ const auto x = static_cast<float>(regs.surface_clip.x);
+ const auto y = static_cast<float>(regs.surface_clip.y);
+ const auto width = static_cast<float>(regs.surface_clip.width);
+ const auto height = static_cast<float>(regs.surface_clip.height);
+ VkViewport viewport{
+ .x = x,
+ .y = y,
+ .width = width != 0.0f ? width : 1.0f,
+ .height = height != 0.0f ? height : 1.0f,
+ .minDepth = 0.0f,
+ .maxDepth = 1.0f,
+ };
+ scheduler.Record([viewport](vk::CommandBuffer cmdbuf) { cmdbuf.SetViewport(0, viewport); });
+ return;
+ }
const bool is_rescaling{texture_cache.IsRescaling()};
const float scale = is_rescaling ? Settings::values.resolution_info.up_factor : 1.0f;
const std::array viewports{
@@ -1009,4 +1029,17 @@ void RasterizerVulkan::ReleaseChannel(s32 channel_id) {
query_cache.EraseChannel(channel_id);
}
+void RasterizerVulkan::BindInlineIndexBuffer() {
+ if (maxwell3d->inline_index_draw_indexes.empty()) {
+ return;
+ }
+ const auto data_count = static_cast<u32>(maxwell3d->inline_index_draw_indexes.size());
+ auto buffer = buffer_cache_runtime.UploadStagingBuffer(data_count);
+ std::memcpy(buffer.mapped_span.data(), maxwell3d->inline_index_draw_indexes.data(), data_count);
+ buffer_cache_runtime.BindIndexBuffer(
+ maxwell3d->regs.draw.topology, maxwell3d->regs.index_buffer.format,
+ maxwell3d->regs.index_buffer.first, maxwell3d->regs.index_buffer.count, buffer.buffer,
+ static_cast<u32>(buffer.offset), data_count);
+}
+
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 4cde3c983..b0bc306f5 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -64,7 +64,7 @@ public:
StateTracker& state_tracker_, Scheduler& scheduler_);
~RasterizerVulkan() override;
- void Draw(bool is_indexed, bool is_instanced) override;
+ void Draw(bool is_indexed, u32 instance_count) override;
void Clear() override;
void DispatchCompute() override;
void ResetCounter(VideoCore::QueryType type) override;
@@ -141,6 +141,8 @@ private:
void UpdateVertexInput(Tegra::Engines::Maxwell3D::Regs& regs);
+ void BindInlineIndexBuffer();
+
Tegra::GPU& gpu;
ScreenInfo& screen_info;
@@ -153,7 +155,6 @@ private:
DescriptorPool descriptor_pool;
UpdateDescriptorQueue update_descriptor_queue;
BlitImageHelper blit_image;
- ASTCDecoderPass astc_decoder_pass;
RenderPassCache render_pass_cache;
TextureCacheRuntime texture_cache_runtime;
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index c04aad08f..929216749 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -144,7 +144,6 @@ private:
using FuncType = TypedCommand<T>;
static_assert(sizeof(FuncType) < sizeof(data), "Lambda is too large");
- recorded_counts++;
command_offset = Common::AlignUp(command_offset, alignof(FuncType));
if (command_offset > sizeof(data) - sizeof(FuncType)) {
return false;
@@ -166,7 +165,7 @@ private:
}
bool Empty() const {
- return recorded_counts == 0;
+ return command_offset == 0;
}
bool HasSubmit() const {
@@ -177,7 +176,6 @@ private:
Command* first = nullptr;
Command* last = nullptr;
- size_t recorded_counts = 0;
size_t command_offset = 0;
bool submit = false;
alignas(std::max_align_t) std::array<u8, 0x8000> data{};
diff --git a/src/video_core/renderer_vulkan/vk_state_tracker.cpp b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
index b87c3be66..edb41b171 100644
--- a/src/video_core/renderer_vulkan/vk_state_tracker.cpp
+++ b/src/video_core/renderer_vulkan/vk_state_tracker.cpp
@@ -51,7 +51,7 @@ Flags MakeInvalidationFlags() {
void SetupDirtyViewports(Tables& tables) {
FillBlock(tables[0], OFF(viewport_transform), NUM(viewport_transform), Viewports);
FillBlock(tables[0], OFF(viewports), NUM(viewports), Viewports);
- tables[0][OFF(viewport_scale_offset_enbled)] = Viewports;
+ tables[0][OFF(viewport_scale_offset_enabled)] = Viewports;
tables[1][OFF(window_origin)] = Viewports;
}
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 305ad8aee..853b80d8a 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -791,12 +791,17 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
MemoryAllocator& memory_allocator_,
StagingBufferPool& staging_buffer_pool_,
BlitImageHelper& blit_image_helper_,
- ASTCDecoderPass& astc_decoder_pass_,
- RenderPassCache& render_pass_cache_)
+ RenderPassCache& render_pass_cache_,
+ DescriptorPool& descriptor_pool,
+ UpdateDescriptorQueue& update_descriptor_queue)
: device{device_}, scheduler{scheduler_}, memory_allocator{memory_allocator_},
staging_buffer_pool{staging_buffer_pool_}, blit_image_helper{blit_image_helper_},
- astc_decoder_pass{astc_decoder_pass_}, render_pass_cache{render_pass_cache_},
- resolution{Settings::values.resolution_info} {}
+ render_pass_cache{render_pass_cache_}, resolution{Settings::values.resolution_info} {
+ if (Settings::values.accelerate_astc) {
+ astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
+ update_descriptor_queue, memory_allocator);
+ }
+}
void TextureCacheRuntime::Finish() {
scheduler.Finish();
@@ -1782,17 +1787,17 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
const auto& resolution = runtime.resolution;
- u32 width = 0;
- u32 height = 0;
+ u32 width = std::numeric_limits<u32>::max();
+ u32 height = std::numeric_limits<u32>::max();
for (size_t index = 0; index < NUM_RT; ++index) {
const ImageView* const color_buffer = color_buffers[index];
if (!color_buffer) {
renderpass_key.color_formats[index] = PixelFormat::Invalid;
continue;
}
- width = std::max(width, is_rescaled ? resolution.ScaleUp(color_buffer->size.width)
+ width = std::min(width, is_rescaled ? resolution.ScaleUp(color_buffer->size.width)
: color_buffer->size.width);
- height = std::max(height, is_rescaled ? resolution.ScaleUp(color_buffer->size.height)
+ height = std::min(height, is_rescaled ? resolution.ScaleUp(color_buffer->size.height)
: color_buffer->size.height);
attachments.push_back(color_buffer->RenderTarget());
renderpass_key.color_formats[index] = color_buffer->format;
@@ -1804,9 +1809,9 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
}
const size_t num_colors = attachments.size();
if (depth_buffer) {
- width = std::max(width, is_rescaled ? resolution.ScaleUp(depth_buffer->size.width)
+ width = std::min(width, is_rescaled ? resolution.ScaleUp(depth_buffer->size.width)
: depth_buffer->size.width);
- height = std::max(height, is_rescaled ? resolution.ScaleUp(depth_buffer->size.height)
+ height = std::min(height, is_rescaled ? resolution.ScaleUp(depth_buffer->size.height)
: depth_buffer->size.height);
attachments.push_back(depth_buffer->RenderTarget());
renderpass_key.depth_format = depth_buffer->format;
@@ -1845,7 +1850,7 @@ void TextureCacheRuntime::AccelerateImageUpload(
Image& image, const StagingBufferRef& map,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
if (IsPixelFormatASTC(image.info.format)) {
- return astc_decoder_pass.Assemble(image, map, swizzles);
+ return astc_decoder_pass->Assemble(image, map, swizzles);
}
ASSERT(false);
}
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 0b7ac0df1..7ec0df134 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -6,6 +6,7 @@
#include <span>
#include "shader_recompiler/shader_info.h"
+#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
#include "video_core/texture_cache/image_view_base.h"
#include "video_core/texture_cache/texture_cache_base.h"
@@ -25,14 +26,15 @@ using VideoCommon::RenderTargets;
using VideoCommon::SlotVector;
using VideoCore::Surface::PixelFormat;
-class ASTCDecoderPass;
class BlitImageHelper;
+class DescriptorPool;
class Device;
class Image;
class ImageView;
class Framebuffer;
class RenderPassCache;
class StagingBufferPool;
+class UpdateDescriptorQueue;
class Scheduler;
class TextureCacheRuntime {
@@ -41,8 +43,9 @@ public:
MemoryAllocator& memory_allocator_,
StagingBufferPool& staging_buffer_pool_,
BlitImageHelper& blit_image_helper_,
- ASTCDecoderPass& astc_decoder_pass_,
- RenderPassCache& render_pass_cache_);
+ RenderPassCache& render_pass_cache_,
+ DescriptorPool& descriptor_pool,
+ UpdateDescriptorQueue& update_descriptor_queue);
void Finish();
@@ -97,8 +100,8 @@ public:
MemoryAllocator& memory_allocator;
StagingBufferPool& staging_buffer_pool;
BlitImageHelper& blit_image_helper;
- ASTCDecoderPass& astc_decoder_pass;
RenderPassCache& render_pass_cache;
+ std::optional<ASTCDecoderPass> astc_decoder_pass;
const Settings::ResolutionScalingInfo& resolution;
constexpr static size_t indexing_slots = 8 * sizeof(size_t);
diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp
index fbabb3219..f24f320b6 100644
--- a/src/video_core/shader_environment.cpp
+++ b/src/video_core/shader_environment.cpp
@@ -19,6 +19,7 @@
#include "video_core/engines/kepler_compute.h"
#include "video_core/memory_manager.h"
#include "video_core/shader_environment.h"
+#include "video_core/texture_cache/format_lookup_table.h"
#include "video_core/textures/texture.h"
namespace VideoCommon {
@@ -33,7 +34,7 @@ static u64 MakeCbufKey(u32 index, u32 offset) {
return (static_cast<u64>(index) << 32) | offset;
}
-static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) {
+static Shader::TextureType ConvertTextureType(const Tegra::Texture::TICEntry& entry) {
switch (entry.texture_type) {
case Tegra::Texture::TextureType::Texture1D:
return Shader::TextureType::Color1D;
@@ -59,6 +60,26 @@ static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) {
}
}
+static Shader::TexturePixelFormat ConvertTexturePixelFormat(const Tegra::Texture::TICEntry& entry) {
+ switch (PixelFormatFromTextureInfo(entry.format, entry.r_type, entry.g_type, entry.b_type,
+ entry.a_type, entry.srgb_conversion)) {
+ case VideoCore::Surface::PixelFormat::A8B8G8R8_SNORM:
+ return Shader::TexturePixelFormat::A8B8G8R8_SNORM;
+ case VideoCore::Surface::PixelFormat::R8_SNORM:
+ return Shader::TexturePixelFormat::R8_SNORM;
+ case VideoCore::Surface::PixelFormat::R8G8_SNORM:
+ return Shader::TexturePixelFormat::R8G8_SNORM;
+ case VideoCore::Surface::PixelFormat::R16G16B16A16_SNORM:
+ return Shader::TexturePixelFormat::R16G16B16A16_SNORM;
+ case VideoCore::Surface::PixelFormat::R16G16_SNORM:
+ return Shader::TexturePixelFormat::R16G16_SNORM;
+ case VideoCore::Surface::PixelFormat::R16_SNORM:
+ return Shader::TexturePixelFormat::R16_SNORM;
+ default:
+ return Shader::TexturePixelFormat::OTHER;
+ }
+}
+
static std::string_view StageToPrefix(Shader::Stage stage) {
switch (stage) {
case Shader::Stage::VertexB:
@@ -178,22 +199,31 @@ void GenericEnvironment::Dump(u64 hash) {
void GenericEnvironment::Serialize(std::ofstream& file) const {
const u64 code_size{static_cast<u64>(CachedSize())};
const u64 num_texture_types{static_cast<u64>(texture_types.size())};
+ const u64 num_texture_pixel_formats{static_cast<u64>(texture_pixel_formats.size())};
const u64 num_cbuf_values{static_cast<u64>(cbuf_values.size())};
file.write(reinterpret_cast<const char*>(&code_size), sizeof(code_size))
.write(reinterpret_cast<const char*>(&num_texture_types), sizeof(num_texture_types))
+ .write(reinterpret_cast<const char*>(&num_texture_pixel_formats),
+ sizeof(num_texture_pixel_formats))
.write(reinterpret_cast<const char*>(&num_cbuf_values), sizeof(num_cbuf_values))
.write(reinterpret_cast<const char*>(&local_memory_size), sizeof(local_memory_size))
.write(reinterpret_cast<const char*>(&texture_bound), sizeof(texture_bound))
.write(reinterpret_cast<const char*>(&start_address), sizeof(start_address))
.write(reinterpret_cast<const char*>(&cached_lowest), sizeof(cached_lowest))
.write(reinterpret_cast<const char*>(&cached_highest), sizeof(cached_highest))
+ .write(reinterpret_cast<const char*>(&viewport_transform_state),
+ sizeof(viewport_transform_state))
.write(reinterpret_cast<const char*>(&stage), sizeof(stage))
.write(reinterpret_cast<const char*>(code.data()), code_size);
for (const auto& [key, type] : texture_types) {
file.write(reinterpret_cast<const char*>(&key), sizeof(key))
.write(reinterpret_cast<const char*>(&type), sizeof(type));
}
+ for (const auto& [key, format] : texture_pixel_formats) {
+ file.write(reinterpret_cast<const char*>(&key), sizeof(key))
+ .write(reinterpret_cast<const char*>(&format), sizeof(format));
+ }
for (const auto& [key, type] : cbuf_values) {
file.write(reinterpret_cast<const char*>(&key), sizeof(key))
.write(reinterpret_cast<const char*>(&type), sizeof(type));
@@ -237,15 +267,13 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
return std::nullopt;
}
-Shader::TextureType GenericEnvironment::ReadTextureTypeImpl(GPUVAddr tic_addr, u32 tic_limit,
- bool via_header_index, u32 raw) {
+Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
+ bool via_header_index, u32 raw) {
const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
const GPUVAddr descriptor_addr{tic_addr + handle.first * sizeof(Tegra::Texture::TICEntry)};
Tegra::Texture::TICEntry entry;
gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry));
- const Shader::TextureType result{ConvertType(entry)};
- texture_types.emplace(raw, result);
- return result;
+ return entry;
}
GraphicsEnvironment::GraphicsEnvironment(Tegra::Engines::Maxwell3D& maxwell3d_,
@@ -305,8 +333,27 @@ u32 GraphicsEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
Shader::TextureType GraphicsEnvironment::ReadTextureType(u32 handle) {
const auto& regs{maxwell3d->regs};
const bool via_header_index{regs.sampler_binding == Maxwell::SamplerBinding::ViaHeaderBinding};
- return ReadTextureTypeImpl(regs.tex_header.Address(), regs.tex_header.limit, via_header_index,
- handle);
+ auto entry =
+ ReadTextureInfo(regs.tex_header.Address(), regs.tex_header.limit, via_header_index, handle);
+ const Shader::TextureType result{ConvertTextureType(entry)};
+ texture_types.emplace(handle, result);
+ return result;
+}
+
+Shader::TexturePixelFormat GraphicsEnvironment::ReadTexturePixelFormat(u32 handle) {
+ const auto& regs{maxwell3d->regs};
+ const bool via_header_index{regs.sampler_binding == Maxwell::SamplerBinding::ViaHeaderBinding};
+ auto entry =
+ ReadTextureInfo(regs.tex_header.Address(), regs.tex_header.limit, via_header_index, handle);
+ const Shader::TexturePixelFormat result(ConvertTexturePixelFormat(entry));
+ texture_pixel_formats.emplace(handle, result);
+ return result;
+}
+
+u32 GraphicsEnvironment::ReadViewportTransformState() {
+ const auto& regs{maxwell3d->regs};
+ viewport_transform_state = regs.viewport_scale_offset_enabled;
+ return viewport_transform_state;
}
ComputeEnvironment::ComputeEnvironment(Tegra::Engines::KeplerCompute& kepler_compute_,
@@ -337,21 +384,41 @@ u32 ComputeEnvironment::ReadCbufValue(u32 cbuf_index, u32 cbuf_offset) {
Shader::TextureType ComputeEnvironment::ReadTextureType(u32 handle) {
const auto& regs{kepler_compute->regs};
const auto& qmd{kepler_compute->launch_description};
- return ReadTextureTypeImpl(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
+ auto entry = ReadTextureInfo(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
+ const Shader::TextureType result{ConvertTextureType(entry)};
+ texture_types.emplace(handle, result);
+ return result;
+}
+
+Shader::TexturePixelFormat ComputeEnvironment::ReadTexturePixelFormat(u32 handle) {
+ const auto& regs{kepler_compute->regs};
+ const auto& qmd{kepler_compute->launch_description};
+ auto entry = ReadTextureInfo(regs.tic.Address(), regs.tic.limit, qmd.linked_tsc != 0, handle);
+ const Shader::TexturePixelFormat result(ConvertTexturePixelFormat(entry));
+ texture_pixel_formats.emplace(handle, result);
+ return result;
+}
+
+u32 ComputeEnvironment::ReadViewportTransformState() {
+ return viewport_transform_state;
}
void FileEnvironment::Deserialize(std::ifstream& file) {
u64 code_size{};
u64 num_texture_types{};
+ u64 num_texture_pixel_formats{};
u64 num_cbuf_values{};
file.read(reinterpret_cast<char*>(&code_size), sizeof(code_size))
.read(reinterpret_cast<char*>(&num_texture_types), sizeof(num_texture_types))
+ .read(reinterpret_cast<char*>(&num_texture_pixel_formats),
+ sizeof(num_texture_pixel_formats))
.read(reinterpret_cast<char*>(&num_cbuf_values), sizeof(num_cbuf_values))
.read(reinterpret_cast<char*>(&local_memory_size), sizeof(local_memory_size))
.read(reinterpret_cast<char*>(&texture_bound), sizeof(texture_bound))
.read(reinterpret_cast<char*>(&start_address), sizeof(start_address))
.read(reinterpret_cast<char*>(&read_lowest), sizeof(read_lowest))
.read(reinterpret_cast<char*>(&read_highest), sizeof(read_highest))
+ .read(reinterpret_cast<char*>(&viewport_transform_state), sizeof(viewport_transform_state))
.read(reinterpret_cast<char*>(&stage), sizeof(stage));
code = std::make_unique<u64[]>(Common::DivCeil(code_size, sizeof(u64)));
file.read(reinterpret_cast<char*>(code.get()), code_size);
@@ -362,6 +429,13 @@ void FileEnvironment::Deserialize(std::ifstream& file) {
.read(reinterpret_cast<char*>(&type), sizeof(type));
texture_types.emplace(key, type);
}
+ for (size_t i = 0; i < num_texture_pixel_formats; ++i) {
+ u32 key;
+ Shader::TexturePixelFormat format;
+ file.read(reinterpret_cast<char*>(&key), sizeof(key))
+ .read(reinterpret_cast<char*>(&format), sizeof(format));
+ texture_pixel_formats.emplace(key, format);
+ }
for (size_t i = 0; i < num_cbuf_values; ++i) {
u64 key;
u32 value;
@@ -409,6 +483,18 @@ Shader::TextureType FileEnvironment::ReadTextureType(u32 handle) {
return it->second;
}
+Shader::TexturePixelFormat FileEnvironment::ReadTexturePixelFormat(u32 handle) {
+ const auto it{texture_pixel_formats.find(handle)};
+ if (it == texture_pixel_formats.end()) {
+ throw Shader::LogicError("Uncached read texture pixel format");
+ }
+ return it->second;
+}
+
+u32 FileEnvironment::ReadViewportTransformState() {
+ return viewport_transform_state;
+}
+
u32 FileEnvironment::LocalMemorySize() const {
return local_memory_size;
}
diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h
index 8b3b8e9f5..bb55b029f 100644
--- a/src/video_core/shader_environment.h
+++ b/src/video_core/shader_environment.h
@@ -63,14 +63,15 @@ public:
protected:
std::optional<u64> TryFindSize();
- Shader::TextureType ReadTextureTypeImpl(GPUVAddr tic_addr, u32 tic_limit, bool via_header_index,
- u32 raw);
+ Tegra::Texture::TICEntry ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
+ bool via_header_index, u32 raw);
Tegra::MemoryManager* gpu_memory{};
GPUVAddr program_base{};
std::vector<u64> code;
std::unordered_map<u32, Shader::TextureType> texture_types;
+ std::unordered_map<u32, Shader::TexturePixelFormat> texture_pixel_formats;
std::unordered_map<u64, u32> cbuf_values;
u32 local_memory_size{};
@@ -85,6 +86,8 @@ protected:
u32 cached_highest = 0;
u32 initial_offset = 0;
+ u32 viewport_transform_state = 1;
+
bool has_unbound_instructions = false;
};
@@ -102,6 +105,10 @@ public:
Shader::TextureType ReadTextureType(u32 handle) override;
+ Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
+
+ u32 ReadViewportTransformState() override;
+
private:
Tegra::Engines::Maxwell3D* maxwell3d{};
size_t stage_index{};
@@ -120,6 +127,10 @@ public:
Shader::TextureType ReadTextureType(u32 handle) override;
+ Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
+
+ u32 ReadViewportTransformState() override;
+
private:
Tegra::Engines::KeplerCompute* kepler_compute{};
};
@@ -143,6 +154,10 @@ public:
[[nodiscard]] Shader::TextureType ReadTextureType(u32 handle) override;
+ [[nodiscard]] Shader::TexturePixelFormat ReadTexturePixelFormat(u32 handle) override;
+
+ [[nodiscard]] u32 ReadViewportTransformState() override;
+
[[nodiscard]] u32 LocalMemorySize() const override;
[[nodiscard]] u32 SharedMemorySize() const override;
@@ -156,6 +171,7 @@ public:
private:
std::unique_ptr<u64[]> code;
std::unordered_map<u32, Shader::TextureType> texture_types;
+ std::unordered_map<u32, Shader::TexturePixelFormat> texture_pixel_formats;
std::unordered_map<u64, u32> cbuf_values;
std::array<u32, 3> workgroup_size{};
u32 local_memory_size{};
@@ -164,6 +180,7 @@ private:
u32 read_lowest{};
u32 read_highest{};
u32 initial_offset{};
+ u32 viewport_transform_state = 1;
};
void SerializePipeline(std::span<const char> key, std::span<const GenericEnvironment* const> envs,
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index 1223df5a0..e8c908b42 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -516,7 +516,6 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
const u32 num_blocks_per_layer = NumBlocks(level_size, tile_size);
const u32 host_bytes_per_layer = num_blocks_per_layer * bytes_per_block;
- UNIMPLEMENTED_IF(info.tile_width_spacing > 0);
UNIMPLEMENTED_IF(copy.image_offset.x != 0);
UNIMPLEMENTED_IF(copy.image_offset.y != 0);
UNIMPLEMENTED_IF(copy.image_offset.z != 0);
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 29d506c47..5cc1fbf32 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -315,7 +315,7 @@ target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
if (NOT WIN32)
target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+if (UNIX AND NOT APPLE)
target_link_libraries(yuzu PRIVATE Qt::DBus)
endif()
@@ -385,6 +385,6 @@ if (NOT APPLE)
target_compile_definitions(yuzu PRIVATE HAS_OPENGL)
endif()
-if (ARCHITECTURE_x86_64)
+if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
target_link_libraries(yuzu PRIVATE dynarmic)
endif()
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 6acfb7b06..d88efacd7 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -401,224 +401,127 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
}
int GRenderWindow::QtKeyToSwitchKey(Qt::Key qt_key) {
- switch (qt_key) {
- case Qt::Key_A:
- return Settings::NativeKeyboard::A;
- case Qt::Key_B:
- return Settings::NativeKeyboard::B;
- case Qt::Key_C:
- return Settings::NativeKeyboard::C;
- case Qt::Key_D:
- return Settings::NativeKeyboard::D;
- case Qt::Key_E:
- return Settings::NativeKeyboard::E;
- case Qt::Key_F:
- return Settings::NativeKeyboard::F;
- case Qt::Key_G:
- return Settings::NativeKeyboard::G;
- case Qt::Key_H:
- return Settings::NativeKeyboard::H;
- case Qt::Key_I:
- return Settings::NativeKeyboard::I;
- case Qt::Key_J:
- return Settings::NativeKeyboard::J;
- case Qt::Key_K:
- return Settings::NativeKeyboard::K;
- case Qt::Key_L:
- return Settings::NativeKeyboard::L;
- case Qt::Key_M:
- return Settings::NativeKeyboard::M;
- case Qt::Key_N:
- return Settings::NativeKeyboard::N;
- case Qt::Key_O:
- return Settings::NativeKeyboard::O;
- case Qt::Key_P:
- return Settings::NativeKeyboard::P;
- case Qt::Key_Q:
- return Settings::NativeKeyboard::Q;
- case Qt::Key_R:
- return Settings::NativeKeyboard::R;
- case Qt::Key_S:
- return Settings::NativeKeyboard::S;
- case Qt::Key_T:
- return Settings::NativeKeyboard::T;
- case Qt::Key_U:
- return Settings::NativeKeyboard::U;
- case Qt::Key_V:
- return Settings::NativeKeyboard::V;
- case Qt::Key_W:
- return Settings::NativeKeyboard::W;
- case Qt::Key_X:
- return Settings::NativeKeyboard::X;
- case Qt::Key_Y:
- return Settings::NativeKeyboard::Y;
- case Qt::Key_Z:
- return Settings::NativeKeyboard::Z;
- case Qt::Key_1:
- return Settings::NativeKeyboard::N1;
- case Qt::Key_2:
- return Settings::NativeKeyboard::N2;
- case Qt::Key_3:
- return Settings::NativeKeyboard::N3;
- case Qt::Key_4:
- return Settings::NativeKeyboard::N4;
- case Qt::Key_5:
- return Settings::NativeKeyboard::N5;
- case Qt::Key_6:
- return Settings::NativeKeyboard::N6;
- case Qt::Key_7:
- return Settings::NativeKeyboard::N7;
- case Qt::Key_8:
- return Settings::NativeKeyboard::N8;
- case Qt::Key_9:
- return Settings::NativeKeyboard::N9;
- case Qt::Key_0:
- return Settings::NativeKeyboard::N0;
- case Qt::Key_Return:
- return Settings::NativeKeyboard::Return;
- case Qt::Key_Escape:
- return Settings::NativeKeyboard::Escape;
- case Qt::Key_Backspace:
- return Settings::NativeKeyboard::Backspace;
- case Qt::Key_Tab:
- return Settings::NativeKeyboard::Tab;
- case Qt::Key_Space:
- return Settings::NativeKeyboard::Space;
- case Qt::Key_Minus:
- return Settings::NativeKeyboard::Minus;
- case Qt::Key_Plus:
- case Qt::Key_questiondown:
- return Settings::NativeKeyboard::Plus;
- case Qt::Key_BracketLeft:
- case Qt::Key_BraceLeft:
- return Settings::NativeKeyboard::OpenBracket;
- case Qt::Key_BracketRight:
- case Qt::Key_BraceRight:
- return Settings::NativeKeyboard::CloseBracket;
- case Qt::Key_Bar:
- return Settings::NativeKeyboard::Pipe;
- case Qt::Key_Dead_Tilde:
- return Settings::NativeKeyboard::Tilde;
- case Qt::Key_Ntilde:
- case Qt::Key_Semicolon:
- return Settings::NativeKeyboard::Semicolon;
- case Qt::Key_Apostrophe:
- return Settings::NativeKeyboard::Quote;
- case Qt::Key_Dead_Grave:
- return Settings::NativeKeyboard::Backquote;
- case Qt::Key_Comma:
- return Settings::NativeKeyboard::Comma;
- case Qt::Key_Period:
- return Settings::NativeKeyboard::Period;
- case Qt::Key_Slash:
- return Settings::NativeKeyboard::Slash;
- case Qt::Key_CapsLock:
- return Settings::NativeKeyboard::CapsLock;
- case Qt::Key_F1:
- return Settings::NativeKeyboard::F1;
- case Qt::Key_F2:
- return Settings::NativeKeyboard::F2;
- case Qt::Key_F3:
- return Settings::NativeKeyboard::F3;
- case Qt::Key_F4:
- return Settings::NativeKeyboard::F4;
- case Qt::Key_F5:
- return Settings::NativeKeyboard::F5;
- case Qt::Key_F6:
- return Settings::NativeKeyboard::F6;
- case Qt::Key_F7:
- return Settings::NativeKeyboard::F7;
- case Qt::Key_F8:
- return Settings::NativeKeyboard::F8;
- case Qt::Key_F9:
- return Settings::NativeKeyboard::F9;
- case Qt::Key_F10:
- return Settings::NativeKeyboard::F10;
- case Qt::Key_F11:
- return Settings::NativeKeyboard::F11;
- case Qt::Key_F12:
- return Settings::NativeKeyboard::F12;
- case Qt::Key_Print:
- return Settings::NativeKeyboard::PrintScreen;
- case Qt::Key_ScrollLock:
- return Settings::NativeKeyboard::ScrollLock;
- case Qt::Key_Pause:
- return Settings::NativeKeyboard::Pause;
- case Qt::Key_Insert:
- return Settings::NativeKeyboard::Insert;
- case Qt::Key_Home:
- return Settings::NativeKeyboard::Home;
- case Qt::Key_PageUp:
- return Settings::NativeKeyboard::PageUp;
- case Qt::Key_Delete:
- return Settings::NativeKeyboard::Delete;
- case Qt::Key_End:
- return Settings::NativeKeyboard::End;
- case Qt::Key_PageDown:
- return Settings::NativeKeyboard::PageDown;
- case Qt::Key_Right:
- return Settings::NativeKeyboard::Right;
- case Qt::Key_Left:
- return Settings::NativeKeyboard::Left;
- case Qt::Key_Down:
- return Settings::NativeKeyboard::Down;
- case Qt::Key_Up:
- return Settings::NativeKeyboard::Up;
- case Qt::Key_NumLock:
- return Settings::NativeKeyboard::NumLock;
- // Numpad keys are missing here
- case Qt::Key_F13:
- return Settings::NativeKeyboard::F13;
- case Qt::Key_F14:
- return Settings::NativeKeyboard::F14;
- case Qt::Key_F15:
- return Settings::NativeKeyboard::F15;
- case Qt::Key_F16:
- return Settings::NativeKeyboard::F16;
- case Qt::Key_F17:
- return Settings::NativeKeyboard::F17;
- case Qt::Key_F18:
- return Settings::NativeKeyboard::F18;
- case Qt::Key_F19:
- return Settings::NativeKeyboard::F19;
- case Qt::Key_F20:
- return Settings::NativeKeyboard::F20;
- case Qt::Key_F21:
- return Settings::NativeKeyboard::F21;
- case Qt::Key_F22:
- return Settings::NativeKeyboard::F22;
- case Qt::Key_F23:
- return Settings::NativeKeyboard::F23;
- case Qt::Key_F24:
- return Settings::NativeKeyboard::F24;
- // case Qt:::
- // return Settings::NativeKeyboard::KPComma;
- // case Qt:::
- // return Settings::NativeKeyboard::Ro;
- case Qt::Key_Hiragana_Katakana:
- return Settings::NativeKeyboard::KatakanaHiragana;
- case Qt::Key_yen:
- return Settings::NativeKeyboard::Yen;
- case Qt::Key_Henkan:
- return Settings::NativeKeyboard::Henkan;
- case Qt::Key_Muhenkan:
- return Settings::NativeKeyboard::Muhenkan;
- // case Qt:::
- // return Settings::NativeKeyboard::NumPadCommaPc98;
- case Qt::Key_Hangul:
- return Settings::NativeKeyboard::HangulEnglish;
- case Qt::Key_Hangul_Hanja:
- return Settings::NativeKeyboard::Hanja;
- case Qt::Key_Katakana:
- return Settings::NativeKeyboard::KatakanaKey;
- case Qt::Key_Hiragana:
- return Settings::NativeKeyboard::HiraganaKey;
- case Qt::Key_Zenkaku_Hankaku:
- return Settings::NativeKeyboard::ZenkakuHankaku;
- // Modifier keys are handled by the modifier property
- default:
- return Settings::NativeKeyboard::None;
+ static constexpr std::array<std::pair<Qt::Key, Settings::NativeKeyboard::Keys>, 106> key_map = {
+ std::pair<Qt::Key, Settings::NativeKeyboard::Keys>{Qt::Key_A, Settings::NativeKeyboard::A},
+ {Qt::Key_A, Settings::NativeKeyboard::A},
+ {Qt::Key_B, Settings::NativeKeyboard::B},
+ {Qt::Key_C, Settings::NativeKeyboard::C},
+ {Qt::Key_D, Settings::NativeKeyboard::D},
+ {Qt::Key_E, Settings::NativeKeyboard::E},
+ {Qt::Key_F, Settings::NativeKeyboard::F},
+ {Qt::Key_G, Settings::NativeKeyboard::G},
+ {Qt::Key_H, Settings::NativeKeyboard::H},
+ {Qt::Key_I, Settings::NativeKeyboard::I},
+ {Qt::Key_J, Settings::NativeKeyboard::J},
+ {Qt::Key_K, Settings::NativeKeyboard::K},
+ {Qt::Key_L, Settings::NativeKeyboard::L},
+ {Qt::Key_M, Settings::NativeKeyboard::M},
+ {Qt::Key_N, Settings::NativeKeyboard::N},
+ {Qt::Key_O, Settings::NativeKeyboard::O},
+ {Qt::Key_P, Settings::NativeKeyboard::P},
+ {Qt::Key_Q, Settings::NativeKeyboard::Q},
+ {Qt::Key_R, Settings::NativeKeyboard::R},
+ {Qt::Key_S, Settings::NativeKeyboard::S},
+ {Qt::Key_T, Settings::NativeKeyboard::T},
+ {Qt::Key_U, Settings::NativeKeyboard::U},
+ {Qt::Key_V, Settings::NativeKeyboard::V},
+ {Qt::Key_W, Settings::NativeKeyboard::W},
+ {Qt::Key_X, Settings::NativeKeyboard::X},
+ {Qt::Key_Y, Settings::NativeKeyboard::Y},
+ {Qt::Key_Z, Settings::NativeKeyboard::Z},
+ {Qt::Key_1, Settings::NativeKeyboard::N1},
+ {Qt::Key_2, Settings::NativeKeyboard::N2},
+ {Qt::Key_3, Settings::NativeKeyboard::N3},
+ {Qt::Key_4, Settings::NativeKeyboard::N4},
+ {Qt::Key_5, Settings::NativeKeyboard::N5},
+ {Qt::Key_6, Settings::NativeKeyboard::N6},
+ {Qt::Key_7, Settings::NativeKeyboard::N7},
+ {Qt::Key_8, Settings::NativeKeyboard::N8},
+ {Qt::Key_9, Settings::NativeKeyboard::N9},
+ {Qt::Key_0, Settings::NativeKeyboard::N0},
+ {Qt::Key_Return, Settings::NativeKeyboard::Return},
+ {Qt::Key_Escape, Settings::NativeKeyboard::Escape},
+ {Qt::Key_Backspace, Settings::NativeKeyboard::Backspace},
+ {Qt::Key_Tab, Settings::NativeKeyboard::Tab},
+ {Qt::Key_Space, Settings::NativeKeyboard::Space},
+ {Qt::Key_Minus, Settings::NativeKeyboard::Minus},
+ {Qt::Key_Plus, Settings::NativeKeyboard::Plus},
+ {Qt::Key_questiondown, Settings::NativeKeyboard::Plus},
+ {Qt::Key_BracketLeft, Settings::NativeKeyboard::OpenBracket},
+ {Qt::Key_BraceLeft, Settings::NativeKeyboard::OpenBracket},
+ {Qt::Key_BracketRight, Settings::NativeKeyboard::CloseBracket},
+ {Qt::Key_BraceRight, Settings::NativeKeyboard::CloseBracket},
+ {Qt::Key_Bar, Settings::NativeKeyboard::Pipe},
+ {Qt::Key_Dead_Tilde, Settings::NativeKeyboard::Tilde},
+ {Qt::Key_Ntilde, Settings::NativeKeyboard::Semicolon},
+ {Qt::Key_Semicolon, Settings::NativeKeyboard::Semicolon},
+ {Qt::Key_Apostrophe, Settings::NativeKeyboard::Quote},
+ {Qt::Key_Dead_Grave, Settings::NativeKeyboard::Backquote},
+ {Qt::Key_Comma, Settings::NativeKeyboard::Comma},
+ {Qt::Key_Period, Settings::NativeKeyboard::Period},
+ {Qt::Key_Slash, Settings::NativeKeyboard::Slash},
+ {Qt::Key_CapsLock, Settings::NativeKeyboard::CapsLockKey},
+ {Qt::Key_F1, Settings::NativeKeyboard::F1},
+ {Qt::Key_F2, Settings::NativeKeyboard::F2},
+ {Qt::Key_F3, Settings::NativeKeyboard::F3},
+ {Qt::Key_F4, Settings::NativeKeyboard::F4},
+ {Qt::Key_F5, Settings::NativeKeyboard::F5},
+ {Qt::Key_F6, Settings::NativeKeyboard::F6},
+ {Qt::Key_F7, Settings::NativeKeyboard::F7},
+ {Qt::Key_F8, Settings::NativeKeyboard::F8},
+ {Qt::Key_F9, Settings::NativeKeyboard::F9},
+ {Qt::Key_F10, Settings::NativeKeyboard::F10},
+ {Qt::Key_F11, Settings::NativeKeyboard::F11},
+ {Qt::Key_F12, Settings::NativeKeyboard::F12},
+ {Qt::Key_Print, Settings::NativeKeyboard::PrintScreen},
+ {Qt::Key_ScrollLock, Settings::NativeKeyboard::ScrollLockKey},
+ {Qt::Key_Pause, Settings::NativeKeyboard::Pause},
+ {Qt::Key_Insert, Settings::NativeKeyboard::Insert},
+ {Qt::Key_Home, Settings::NativeKeyboard::Home},
+ {Qt::Key_PageUp, Settings::NativeKeyboard::PageUp},
+ {Qt::Key_Delete, Settings::NativeKeyboard::Delete},
+ {Qt::Key_End, Settings::NativeKeyboard::End},
+ {Qt::Key_PageDown, Settings::NativeKeyboard::PageDown},
+ {Qt::Key_Right, Settings::NativeKeyboard::Right},
+ {Qt::Key_Left, Settings::NativeKeyboard::Left},
+ {Qt::Key_Down, Settings::NativeKeyboard::Down},
+ {Qt::Key_Up, Settings::NativeKeyboard::Up},
+ {Qt::Key_NumLock, Settings::NativeKeyboard::NumLockKey},
+ // Numpad keys are missing here
+ {Qt::Key_F13, Settings::NativeKeyboard::F13},
+ {Qt::Key_F14, Settings::NativeKeyboard::F14},
+ {Qt::Key_F15, Settings::NativeKeyboard::F15},
+ {Qt::Key_F16, Settings::NativeKeyboard::F16},
+ {Qt::Key_F17, Settings::NativeKeyboard::F17},
+ {Qt::Key_F18, Settings::NativeKeyboard::F18},
+ {Qt::Key_F19, Settings::NativeKeyboard::F19},
+ {Qt::Key_F20, Settings::NativeKeyboard::F20},
+ {Qt::Key_F21, Settings::NativeKeyboard::F21},
+ {Qt::Key_F22, Settings::NativeKeyboard::F22},
+ {Qt::Key_F23, Settings::NativeKeyboard::F23},
+ {Qt::Key_F24, Settings::NativeKeyboard::F24},
+ // {Qt::..., Settings::NativeKeyboard::KPComma},
+ // {Qt::..., Settings::NativeKeyboard::Ro},
+ {Qt::Key_Hiragana_Katakana, Settings::NativeKeyboard::KatakanaHiragana},
+ {Qt::Key_yen, Settings::NativeKeyboard::Yen},
+ {Qt::Key_Henkan, Settings::NativeKeyboard::Henkan},
+ {Qt::Key_Muhenkan, Settings::NativeKeyboard::Muhenkan},
+ // {Qt::..., Settings::NativeKeyboard::NumPadCommaPc98},
+ {Qt::Key_Hangul, Settings::NativeKeyboard::HangulEnglish},
+ {Qt::Key_Hangul_Hanja, Settings::NativeKeyboard::Hanja},
+ {Qt::Key_Katakana, Settings::NativeKeyboard::KatakanaKey},
+ {Qt::Key_Hiragana, Settings::NativeKeyboard::HiraganaKey},
+ {Qt::Key_Zenkaku_Hankaku, Settings::NativeKeyboard::ZenkakuHankaku},
+ // Modifier keys are handled by the modifier property
+ };
+
+ for (const auto& [qkey, nkey] : key_map) {
+ if (qt_key == qkey) {
+ return nkey;
+ }
}
+
+ return Settings::NativeKeyboard::None;
}
int GRenderWindow::QtModifierToSwitchModifier(Qt::KeyboardModifiers qt_modifiers) {
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 195074bf2..343f3b8e5 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -819,6 +819,9 @@ void Config::ReadUIGamelistValues() {
qt_config->beginGroup(QStringLiteral("UIGameList"));
ReadBasicSetting(UISettings::values.show_add_ons);
+ ReadBasicSetting(UISettings::values.show_compat);
+ ReadBasicSetting(UISettings::values.show_size);
+ ReadBasicSetting(UISettings::values.show_types);
ReadBasicSetting(UISettings::values.game_icon_size);
ReadBasicSetting(UISettings::values.folder_icon_size);
ReadBasicSetting(UISettings::values.row_1_text_id);
@@ -1414,6 +1417,9 @@ void Config::SaveUIGamelistValues() {
qt_config->beginGroup(QStringLiteral("UIGameList"));
WriteBasicSetting(UISettings::values.show_add_ons);
+ WriteBasicSetting(UISettings::values.show_compat);
+ WriteBasicSetting(UISettings::values.show_size);
+ WriteBasicSetting(UISettings::values.show_types);
WriteBasicSetting(UISettings::values.game_icon_size);
WriteBasicSetting(UISettings::values.folder_icon_size);
WriteBasicSetting(UISettings::values.row_1_text_id);
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index 48f71b53c..2ebb80302 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -72,6 +72,9 @@ ConfigureUi::ConfigureUi(Core::System& system_, QWidget* parent)
// Force game list reload if any of the relevant settings are changed.
connect(ui->show_add_ons, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
+ connect(ui->show_compat, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
+ connect(ui->show_size, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
+ connect(ui->show_types, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigureUi::RequestGameListUpdate);
connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
@@ -109,6 +112,9 @@ void ConfigureUi::ApplyConfiguration() {
UISettings::values.theme =
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
+ UISettings::values.show_compat = ui->show_compat->isChecked();
+ UISettings::values.show_size = ui->show_size->isChecked();
+ UISettings::values.show_types = ui->show_types->isChecked();
UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
@@ -129,6 +135,9 @@ void ConfigureUi::SetConfiguration() {
ui->language_combobox->setCurrentIndex(
ui->language_combobox->findData(UISettings::values.language));
ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue());
+ ui->show_compat->setChecked(UISettings::values.show_compat.GetValue());
+ ui->show_size->setChecked(UISettings::values.show_size.GetValue());
+ ui->show_types->setChecked(UISettings::values.show_types.GetValue());
ui->game_icon_size_combobox->setCurrentIndex(
ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
ui->folder_icon_size_combobox->setCurrentIndex(
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index a50df7f6f..10bb27312 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>363</width>
- <height>507</height>
+ <height>562</height>
</rect>
</property>
<property name="windowTitle">
@@ -77,6 +77,13 @@
<item>
<layout class="QVBoxLayout" name="GeneralVerticalLayout">
<item>
+ <widget class="QCheckBox" name="show_compat">
+ <property name="text">
+ <string>Show Compatibility List</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QCheckBox" name="show_add_ons">
<property name="text">
<string>Show Add-Ons Column</string>
@@ -84,6 +91,20 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="show_size">
+ <property name="text">
+ <string>Show Size Column</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="show_types">
+ <property name="text">
+ <string>Show File Types Column</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
<item>
<widget class="QLabel" name="game_icon_size_label">
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index b127badc2..5c33c1b0f 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -335,6 +335,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs_, FileSys::ManualContentProvid
RetranslateUI();
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
+ tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
item_model->setSortRole(GameListItemPath::SortRole);
connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::OnUpdateThemedIcons);
@@ -786,6 +787,9 @@ void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
// Update the columns in case UISettings has changed
tree_view->setColumnHidden(COLUMN_ADD_ONS, !UISettings::values.show_add_ons);
+ tree_view->setColumnHidden(COLUMN_COMPATIBILITY, !UISettings::values.show_compat);
+ tree_view->setColumnHidden(COLUMN_FILE_TYPE, !UISettings::values.show_types);
+ tree_view->setColumnHidden(COLUMN_SIZE, !UISettings::values.show_size);
// Delete any rows that might already exist if we're repopulating
item_model->removeRows(0, item_model->rowCount());
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index ccae2b828..d95915016 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -9,7 +9,7 @@
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
-#ifdef __linux__
+#ifdef __unix__
#include <csignal>
#include <sys/socket.h>
#endif
@@ -275,7 +275,7 @@ static void OverrideWindowsFont() {
#endif
bool GMainWindow::CheckDarkMode() {
-#ifdef __linux__
+#ifdef __unix__
const QPalette test_palette(qApp->palette());
const QColor text_color = test_palette.color(QPalette::Active, QPalette::Text);
const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
@@ -283,7 +283,7 @@ bool GMainWindow::CheckDarkMode() {
#else
// TODO: Windows
return false;
-#endif // __linux__
+#endif // __unix__
}
GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan)
@@ -291,7 +291,7 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)},
vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
provider{std::make_unique<FileSys::ManualContentProvider>()} {
-#ifdef __linux__
+#ifdef __unix__
SetupSigInterrupts();
#endif
system->Initialize();
@@ -342,6 +342,7 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
const auto override_build =
fmt::format(fmt::runtime(std::string(Common::g_title_bar_format_idle)), build_id);
const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
+ const auto processor_count = std::thread::hardware_concurrency();
LOG_INFO(Frontend, "yuzu Version: {}", yuzu_build_version);
LogRuntimes();
@@ -361,6 +362,7 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
}
LOG_INFO(Frontend, "Host CPU: {}", cpu_string);
#endif
+ LOG_INFO(Frontend, "Host CPU Threads: {}", processor_count);
LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString());
LOG_INFO(Frontend, "Host RAM: {:.2f} GiB",
Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB});
@@ -509,7 +511,7 @@ GMainWindow::~GMainWindow() {
delete render_window;
}
-#ifdef __linux__
+#ifdef __unix__
::close(sig_interrupt_fds[0]);
::close(sig_interrupt_fds[1]);
#endif
@@ -1379,7 +1381,7 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
}
void GMainWindow::SetupPrepareForSleep() {
-#ifdef __linux__
+#ifdef __unix__
auto bus = QDBusConnection::systemBus();
if (bus.isConnected()) {
const bool success = bus.connect(
@@ -1393,7 +1395,7 @@ void GMainWindow::SetupPrepareForSleep() {
} else {
LOG_WARNING(Frontend, "QDBusConnection system bus is not connected");
}
-#endif // __linux__
+#endif // __unix__
}
void GMainWindow::OnPrepareForSleep(bool prepare_sleep) {
@@ -1415,7 +1417,7 @@ void GMainWindow::OnPrepareForSleep(bool prepare_sleep) {
}
}
-#ifdef __linux__
+#ifdef __unix__
static std::optional<QDBusObjectPath> HoldWakeLockLinux(u32 window_id = 0) {
if (!QDBusConnection::sessionBus().isConnected()) {
return {};
@@ -1500,14 +1502,14 @@ void GMainWindow::OnSigInterruptNotifierActivated() {
emit SigInterrupt();
}
-#endif // __linux__
+#endif // __unix__
void GMainWindow::PreventOSSleep() {
#ifdef _WIN32
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
#elif defined(HAVE_SDL2)
SDL_DisableScreenSaver();
-#ifdef __linux__
+#ifdef __unix__
auto reply = HoldWakeLockLinux(winId());
if (reply) {
wake_lock = std::move(reply.value());
@@ -1521,7 +1523,7 @@ void GMainWindow::AllowOSSleep() {
SetThreadExecutionState(ES_CONTINUOUS);
#elif defined(HAVE_SDL2)
SDL_EnableScreenSaver();
-#ifdef __linux__
+#ifdef __unix__
if (!wake_lock.path().isEmpty()) {
ReleaseWakeLockLinux(wake_lock);
}
@@ -2018,38 +2020,50 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
return true;
}
+QString GMainWindow::GetGameListErrorRemoving(InstalledEntryType type) const {
+ switch (type) {
+ case InstalledEntryType::Game:
+ return tr("Error Removing Contents");
+ case InstalledEntryType::Update:
+ return tr("Error Removing Update");
+ case InstalledEntryType::AddOnContent:
+ return tr("Error Removing DLC");
+ default:
+ return QStringLiteral("Error Removing <Invalid Type>");
+ }
+}
void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) {
- const QString entry_type = [type] {
+ const QString entry_question = [type] {
switch (type) {
case InstalledEntryType::Game:
- return tr("Contents");
+ return tr("Remove Installed Game Contents?");
case InstalledEntryType::Update:
- return tr("Update");
+ return tr("Remove Installed Game Update?");
case InstalledEntryType::AddOnContent:
- return tr("DLC");
+ return tr("Remove Installed Game DLC?");
default:
- return QString{};
+ return QStringLiteral("Remove Installed Game <Invalid Type>?");
}
}();
- if (QMessageBox::question(
- this, tr("Remove Entry"), tr("Remove Installed Game %1?").arg(entry_type),
- QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) {
+ if (QMessageBox::question(this, tr("Remove Entry"), entry_question,
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No) != QMessageBox::Yes) {
return;
}
switch (type) {
case InstalledEntryType::Game:
- RemoveBaseContent(program_id, entry_type);
+ RemoveBaseContent(program_id, type);
[[fallthrough]];
case InstalledEntryType::Update:
- RemoveUpdateContent(program_id, entry_type);
+ RemoveUpdateContent(program_id, type);
if (type != InstalledEntryType::Game) {
break;
}
[[fallthrough]];
case InstalledEntryType::AddOnContent:
- RemoveAddOnContent(program_id, entry_type);
+ RemoveAddOnContent(program_id, type);
break;
}
Common::FS::RemoveDirRecursively(Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) /
@@ -2057,7 +2071,7 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
game_list->PopulateAsync(UISettings::values.game_dirs);
}
-void GMainWindow::RemoveBaseContent(u64 program_id, const QString& entry_type) {
+void GMainWindow::RemoveBaseContent(u64 program_id, InstalledEntryType type) {
const auto& fs_controller = system->GetFileSystemController();
const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(program_id) ||
fs_controller.GetSDMCContents()->RemoveExistingEntry(program_id);
@@ -2067,12 +2081,12 @@ void GMainWindow::RemoveBaseContent(u64 program_id, const QString& entry_type) {
tr("Successfully removed the installed base game."));
} else {
QMessageBox::warning(
- this, tr("Error Removing %1").arg(entry_type),
+ this, GetGameListErrorRemoving(type),
tr("The base game is not installed in the NAND and cannot be removed."));
}
}
-void GMainWindow::RemoveUpdateContent(u64 program_id, const QString& entry_type) {
+void GMainWindow::RemoveUpdateContent(u64 program_id, InstalledEntryType type) {
const auto update_id = program_id | 0x800;
const auto& fs_controller = system->GetFileSystemController();
const auto res = fs_controller.GetUserNANDContents()->RemoveExistingEntry(update_id) ||
@@ -2082,12 +2096,12 @@ void GMainWindow::RemoveUpdateContent(u64 program_id, const QString& entry_type)
QMessageBox::information(this, tr("Successfully Removed"),
tr("Successfully removed the installed update."));
} else {
- QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type),
+ QMessageBox::warning(this, GetGameListErrorRemoving(type),
tr("There is no update installed for this title."));
}
}
-void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type) {
+void GMainWindow::RemoveAddOnContent(u64 program_id, InstalledEntryType type) {
u32 count{};
const auto& fs_controller = system->GetFileSystemController();
const auto dlc_entries = system->GetContentProvider().ListEntriesFilter(
@@ -2105,7 +2119,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type)
}
if (count == 0) {
- QMessageBox::warning(this, tr("Error Removing %1").arg(entry_type),
+ QMessageBox::warning(this, GetGameListErrorRemoving(type),
tr("There are no DLC installed for this title."));
return;
}
@@ -4084,7 +4098,7 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
}
void GMainWindow::changeEvent(QEvent* event) {
-#ifdef __linux__
+#ifdef __unix__
// PaletteChange event appears to only reach so far into the GUI, explicitly asking to
// UpdateUITheme is a decent work around
if (event->type() == QEvent::PaletteChange) {
@@ -4099,7 +4113,7 @@ void GMainWindow::changeEvent(QEvent* event) {
}
last_window_color = window_color;
}
-#endif // __linux__
+#endif // __unix__
QWidget::changeEvent(event);
}
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index f7aa8e417..b73f550dd 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -15,7 +15,7 @@
#include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h"
-#ifdef __linux__
+#ifdef __unix__
#include <QVariant>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QtDBus>
@@ -255,7 +255,7 @@ private:
void changeEvent(QEvent* event) override;
void closeEvent(QCloseEvent* event) override;
-#ifdef __linux__
+#ifdef __unix__
void SetupSigInterrupts();
static void HandleSigInterrupt(int);
void OnSigInterruptNotifierActivated();
@@ -324,9 +324,10 @@ private slots:
void OnMouseActivity();
private:
- void RemoveBaseContent(u64 program_id, const QString& entry_type);
- void RemoveUpdateContent(u64 program_id, const QString& entry_type);
- void RemoveAddOnContent(u64 program_id, const QString& entry_type);
+ QString GetGameListErrorRemoving(InstalledEntryType type) const;
+ void RemoveBaseContent(u64 program_id, InstalledEntryType type);
+ void RemoveUpdateContent(u64 program_id, InstalledEntryType type);
+ void RemoveAddOnContent(u64 program_id, InstalledEntryType type);
void RemoveTransferableShaderCache(u64 program_id, GameListRemoveTarget target);
void RemoveAllTransferableShaderCaches(u64 program_id);
void RemoveCustomConfiguration(u64 program_id, const std::string& game_path);
@@ -435,7 +436,7 @@ private:
// True if TAS recording dialog is visible
bool is_tas_recording_dialog_active{};
-#ifdef __linux__
+#ifdef __unix__
QSocketNotifier* sig_interrupt_notifier;
static std::array<int, 3> sig_interrupt_fds;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 74d49dbd4..e670acc30 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -55,7 +55,6 @@
<addaction name="separator"/>
<addaction name="menu_recent_files"/>
<addaction name="separator"/>
- <addaction name="separator"/>
<addaction name="action_Load_Amiibo"/>
<addaction name="separator"/>
<addaction name="action_Open_yuzu_Folder"/>
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 753797efc..452038cd9 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -129,6 +129,13 @@ struct Values {
Settings::Setting<bool> favorites_expanded{true, "favorites_expanded"};
QVector<u64> favorited_ids;
+ // Compatibility List
+ Settings::Setting<bool> show_compat{false, "show_compat"};
+
+ // Size & File Types Column
+ Settings::Setting<bool> show_size{true, "show_size"};
+ Settings::Setting<bool> show_types{true, "show_types"};
+
bool configuration_applied;
bool reset_to_defaults;
Settings::Setting<bool> disable_web_applet{true, "disable_web_applet"};